Configuration

Application code - whether it's using Blueprint, SCR or any other OSGi techniques - should not create instances of javax.jms.ConnectionFactory directly. The code should find the connection factories registered in OSGi service registry. The connection factories are registered using:

org.osgi.framework.BundleContext.registerService(javax.jms.ConnectionFactory.class,
                                                 connectionFactoryObject,
                                                 properties);
org.osgi.framework.BundleContext.registerService(javax.jms.XAConnectionFactory.class,
                                                 xaConnectionFactoryObject,
                                                 properties);

And PAX-JMS helps with the registration.

There are generally two methods to register connection factories:

  • configuration method, where the connection factories are created on the basis of Configuration Admin configurations
  • deployment method, where the connection factories are registered directly - including the convenient method, where the application registers broker-specific, non-pooling, non-enlisting connection factory, while PAX-JMS wraps it with generic, pooling and enlisting connection factory

OSGi JMS Service

First, an analogy to JDBC.

OSGi way of handling JDBC data sources is related to two interfaces:

  • standard org.osgi.service.jdbc.DataSourceFactory (see Chapter 125 of OSGi Enterprise R6 specification) that's used to create instances of javax.sql.DataSource, javax.sql.XADataSource, javax.sql.ConnectionPoolDataSource and java.sql.Driver
  • proprietary PAX JDBC org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory that creates pooling data sources

For JMS we have these analogous interfaces:

  • proprietary org.ops4j.pax.jms.service.ConnectionFactoryFactory with the same purpose as standard OSGi JDBC org.osgi.service.jdbc.DataSourceFactory
  • proprietary org.ops4j.pax.jms.service.PooledConnectionFactoryFactory with the same purpose as proprietary PAX JDBC org.ops4j.pax.jdbc.pool.common.PooledDataSourceFactory

Dedicated, broker-specific org.ops4j.pax.jms.service.ConnectionFactoryFactory implementations

There are bundles like:

  • mvn:org.ops4j.pax.jms/pax-jms-activemq/1.0.0
  • mvn:org.ops4j.pax.jms/pax-jms-artemis/1.0.0
  • mvn:org.ops4j.pax.jms/pax-jms-ibmmq/1.0.0

that register broker-specific org.ops4j.pax.jms.service.ConnectionFactoryFactory services that can return JMS factories (javax.jms.ConnectionFactory and javax.jms.XAConnectionFactory). For example:

karaf@root()> feature:repo-add mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features
Adding feature url mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features

karaf@root()> feature:repo-add mvn:org.apache.activemq/artemis-features/2.5.0/xml/features                                                                                                                                                     
Adding feature url mvn:org.apache.activemq/artemis-features/2.5.0/xml/features

karaf@root()> feature:install pax-jms-config pax-jms-artemis

karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-config                                                                                                                                                                              

OPS4J Pax JMS Config (72) provides:
-----------------------------------
objectClass = [org.osgi.service.cm.ManagedServiceFactory]
service.bundleid = 72
service.id = 88
service.pid = org.ops4j.connectionfactory
service.scope = singleton

karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-artemis 

OPS4J Pax JMS Artemis Support (71) provides:
--------------------------------------------
objectClass = [org.ops4j.pax.jms.service.ConnectionFactoryFactory]
service.bundleid = 71
service.id = 86
service.scope = singleton
type = artemis

pax-jms-config bundle registers managed service factory for org.ops4j.connectionfactory factory PIDs (described later).

org.ops4j.pax.jms.pax-jms-artemis registers org.ops4j.pax.jms.service.ConnectionFactoryFactory with type=artemis.

PAX-JMS configuration service

mvn:org.ops4j.pax.jms/pax-jms-config/1.0.0 bundle provides a Managed Service Factory that does three things:

  • tracks org.ops4j.pax.jms.service.ConnectionFactoryFactory OSGi services in order to invoke its methods:
public ConnectionFactory createConnectionFactory(Map<String, Object> properties);

public XAConnectionFactory createXAConnectionFactory(Map<String, Object> properties);
  • tracks org.ops4j.connectionfactory factory PIDs in order to collect properties required by the above methods. If we create a factory configuration using any method available for Configuration Admin service, for example by creating ${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg file, we can perform the final step to expose actual broker-specific connection factory (the configuration method).
  • tracks instances of javax.jms.ConnectionFactory and javax.jms.XAConnectionFactory with pool=<pool-name> OSGi service property - to wrap broker-specific connection factories inside pooling connection factories (the deployment method)

Here’s the detailed, canonical step-by-step guide to register Artemis connection factory using configuration method:

1. Install Artemis features and PAX JMS features:

karaf@root()> feature:repo-add mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features
Adding feature url mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features

karaf@root()> feature:repo-add mvn:org.apache.activemq/artemis-features/2.5.0/xml/features
Adding feature url mvn:org.apache.activemq/artemis-features/2.5.0/xml/features

karaf@root()> feature:install pax-jms-config pax-jms-artemis

2. Create factory configuration:

karaf@root()> config:edit --factory --alias artemis org.ops4j.connectionfactory
karaf@root()> config:property-set type artemis
karaf@root()> config:property-set osgi.jndi.service.name artemis # "name" property may be used too
karaf@root()> config:property-set connectionFactoryType ConnectionFactory # or XAConnectionFactory
karaf@root()> config:property-set jms.url tcp://localhost:61616
karaf@root()> config:property-set jms.user pax
karaf@root()> config:property-set jms.password pax
karaf@root()> config:property-set jms.prefetchPolicy.queuePrefetch 1234
karaf@root()> config:update

karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)'
----------------------------------------------------------------
Pid:            org.ops4j.connectionfactory.f5ab1f99-cbfe-48fa-8b34-4e85084f82a8
FactoryPid:     org.ops4j.connectionfactory
BundleLocation: ?
Properties:
   connectionFactoryType = ConnectionFactory
   felix.fileinstall.filename = file:/data/servers/apache-karaf-4.2.0/etc/org.ops4j.connectionfactory-5144692137911418633.cfg
   jms.password = pax
   jms.prefetchPolicy.queuePrefetch = 1234
   jms.url = tcp://localhost:61616
   jms.user = pax
   osgi.jndi.service.name = artemis
   service.factoryPid = org.ops4j.connectionfactory
   service.pid = org.ops4j.connectionfactory.f5ab1f99-cbfe-48fa-8b34-4e85084f82a8
   type = artemis

3. Check if pax-jms-config processed the configuration into javax.jms.ConnectionFactory service:

karaf@root()> service:list javax.jms.ConnectionFactory                                                                                                                                                                                         
[javax.jms.ConnectionFactory]
-----------------------------
 connectionFactoryType = ConnectionFactory
 felix.fileinstall.filename = file:/data/servers/apache-karaf-4.2.0/etc/org.ops4j.connectionfactory-5144692137911418633.cfg
 jms.password = pax
 jms.prefetchPolicy.queuePrefetch = 1234
 jms.url = tcp://localhost:61616
 jms.user = pax
 osgi.jndi.service.name = artemis
 pax.jms.managed = true
 service.bundleid = 72
 service.factoryPid = org.ops4j.connectionfactory
 service.id = 93
 service.pid = org.ops4j.connectionfactory.f5ab1f99-cbfe-48fa-8b34-4e85084f82a8
 service.scope = singleton
 type = artemis
Provided by : 
 OPS4J Pax JMS Config (72)


If we specify additional property for Artemis configuration - protocol=amqp, QPID JMS library would be used instead of Artemis JMS client. amqp:// protocol has to be used then for jms.url property.

Now we have actual broker-specific (no pooling yet) connection factory. We can already inject it where needed. For example we can use Karaf commands from jms feature:

karaf@root()> feature:install -v jms                                                                                                                                                                                                           
Adding features: jms/[4.2.0,4.2.0]
...

karaf@root()> jms:connectionfactories 
JMS Connection Factory
──────────────────────
artemis

karaf@root()> jms:info -u pax -p pax artemis                                                                                                                                                                                              
Property │ Value
─────────┼─────────
product  │ ActiveMQ
version  │ 2.5.0

karaf@root()> jms:send -u pax -p pax artemis DEV.QUEUE.1 "Hello Artemis"

karaf@root()> jms:browser -u pax -p pax artemis DEV.QUEUE.1
Message ID                              │ Content       │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination                │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp
────────────────────────────────────────┼───────────────┼─────────┼──────┼────────────────┼───────────────┼────────────────────────────┼────────────┼──────────┼─────────────┼─────────┼──────────────────────────────
ID:91ac54e1-5371-11e8-a61b-fe5400008fbb │ Hello Artemis │ UTF-8   │      │                │ Persistent    │ ActiveMQQueue[DEV.QUEUE.1] │ Never      │ 4        │ false       │         │ Wed May 09 12:13:07 CEST 2018

Let’s switch the protocol:

karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)'                                                                                                                                                                   
----------------------------------------------------------------
Pid:            org.ops4j.connectionfactory.f5ab1f99-cbfe-48fa-8b34-4e85084f82a8
FactoryPid:     org.ops4j.connectionfactory
BundleLocation: ?
Properties:
   connectionFactoryType = ConnectionFactory
   felix.fileinstall.filename = file:/data/servers/apache-karaf-4.2.0/etc/org.ops4j.connectionfactory-5144692137911418633.cfg
   jms.password = pax
   jms.prefetchPolicy.queuePrefetch = 1234
   jms.url = tcp://localhost:61616
   jms.user = pax
   osgi.jndi.service.name = artemis
   service.factoryPid = org.ops4j.connectionfactory
   service.pid = org.ops4j.connectionfactory.f5ab1f99-cbfe-48fa-8b34-4e85084f82a8
   type = artemis

karaf@root()> config:edit org.ops4j.connectionfactory.f5ab1f99-cbfe-48fa-8b34-4e85084f82a8 
karaf@root()> config:property-set protocol amqp                                                                                                                                                                                                
karaf@root()> config:property-delete user                                                                                                                                                                                                      
karaf@root()> config:property-set username pax # mind the difference between artemis-jms-client and qpid-jms-client                                                                                                                           
karaf@root()> config:property-set jms.url amqp://localhost:61616                                                                                                                                                                               
karaf@root()> config:update    

karaf@root()> jms:info -u pax -p pax artemis
Property │ Value
─────────┼────────
product  │ QpidJMS
version  │ 0.30.0

karaf@root()> jms:browse -u pax -p pax artemis DEV.QUEUE.1
Message ID │ Content       │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp
───────────┼───────────────┼─────────┼──────┼────────────────┼───────────────┼─────────────┼────────────┼──────────┼─────────────┼─────────┼──────────────────────────────
           │ Hello Artemis │ UTF-8   │      │                │ Persistent    │ DEV.QUEUE.1 │ Never      │ 4        │ false       │         │ Wed May 09 12:13:07 CEST 2018

If using PAX JMS with Artemis features for embedded broker (not client ones), consider using self implemented feature to avoid classloading problems.


For completeness, let’s see how we can connect to IBM MQ 9. Even if pax-jms-ibmmq installs relevant PAX JMS bundles, IBM MQ driver is not installed due to licensing reasons.

As mentioned in Message Brokers we can download drivers from IBM MQ page.

karaf@root()> install 'file:///data/downloads/ibm.com/IBM%20MQ/MQSeriesJava-9.0.5-0.x86_64/java/lib/OSGi/com.ibm.msg.client.osgi.commonservices.j2se_9.0.5.0.jar'                                                                              
Bundle ID: 80
karaf@root()> install 'file:///data/downloads/ibm.com/IBM%20MQ/MQSeriesJava-9.0.5-0.x86_64/java/lib/OSGi/com.ibm.msg.client.osgi.jms_9.0.5.0.jar'                                                                                              
Bundle ID: 81
karaf@root()> install 'file:///data/downloads/ibm.com/IBM%20MQ/MQSeriesJava-9.0.5-0.x86_64/java/lib/OSGi/com.ibm.msg.client.osgi.nls_9.0.5.0.jar'                                                                                              
Bundle ID: 82
karaf@root()> install 'file:///data/downloads/ibm.com/IBM%20MQ/MQSeriesJava-9.0.5-0.x86_64/java/lib/OSGi/com.ibm.msg.client.osgi.wmq.nls_9.0.5.0.jar' # this is fragment bundle                                                                                         
Bundle ID: 83
karaf@root()> install 'file:///data/downloads/ibm.com/IBM%20MQ/MQSeriesJava-9.0.5-0.x86_64/java/lib/OSGi/com.ibm.msg.client.osgi.wmq.prereq_9.0.5.0.jar'                                                                                       
Bundle ID: 84
karaf@root()> install 'file:///data/downloads/ibm.com/IBM%20MQ/MQSeriesJava-9.0.5-0.x86_64/java/lib/OSGi/com.ibm.msg.client.osgi.wmq_9.0.5.0.jar'                                                                                              
Bundle ID: 85
karaf@root()> resolve

karaf@root()> start 80 81 82 84 85

These bundles could also be put into custom feature and installed using feature:install command.

And now:

karaf@root()> feature:install pax-jms-ibmmq 
                                                                                                                                                                                                   
karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-ibmmq                                                                                                                                                                               

OPS4J Pax JMS IBM MQ Support (86) provides:
-------------------------------------------
objectClass = [org.ops4j.pax.jms.service.ConnectionFactoryFactory]
service.bundleid = 86
service.id = 103
service.scope = singleton
type = ibmmq

Create factory configuration:

karaf@root()> config:edit --factory --alias ibmmq org.ops4j.connectionfactory
karaf@root()> config:property-set type ibmmq
karaf@root()> config:property-set osgi.jndi.service.name mq9
karaf@root()> config:property-set connectionFactoryType ConnectionFactory # or XAConnectionFactory
karaf@root()> config:property-set jms.queueManager PAXQM
karaf@root()> config:property-set jms.hostName localhost
karaf@root()> config:property-set jms.port 1414
karaf@root()> config:property-set jms.transportType 1 # com.ibm.msg.client.wmq.WMQConstants.WMQ_CM_CLIENT
karaf@root()> config:property-set jms.channel DEV.APP.SVRCONN
karaf@root()> config:property-set jms.CCSID 1208 # com.ibm.msg.client.jms.JmsConstants.CCSID_UTF8
karaf@root()> config:update

karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)'                                                                                                                                                                   
----------------------------------------------------------------
Pid:            org.ops4j.connectionfactory.109a6ced-1ace-4420-af45-daa886ddfd8f
FactoryPid:     org.ops4j.connectionfactory
BundleLocation: ?
Properties:
   connectionFactoryType = ConnectionFactory
   felix.fileinstall.filename = file:/data/servers/apache-karaf-4.2.0/etc/org.ops4j.connectionfactory-2202048893888780045.cfg
   jms.CCSID = 1208
   jms.channel = DEV.APP.SVRCONN
   jms.hostName = localhost
   jms.port = 1414
   jms.queueManager = PAXQM
   jms.transportType = 1
   osgi.jndi.service.name = mq9
   service.factoryPid = org.ops4j.connectionfactory
   service.pid = org.ops4j.connectionfactory.109a6ced-1ace-4420-af45-daa886ddfd8f
   type = ibmmq

Check if pax-jms-config processed the configuration into javax.jms.ConnectionFactory service:

karaf@root()> service:list javax.jms.ConnectionFactory                                                                                                                                                                                         
[javax.jms.ConnectionFactory]
-----------------------------
 connectionFactoryType = ConnectionFactory
 felix.fileinstall.filename = file:/data/servers/apache-karaf-4.2.0/etc/org.ops4j.connectionfactory-2202048893888780045.cfg
 jms.CCSID = 1208
 jms.channel = DEV.APP.SVRCONN
 jms.hostName = localhost
 jms.port = 1414
 jms.queueManager = PAXQM
 jms.transportType = 1
 osgi.jndi.service.name = mq9
 pax.jms.managed = true
 service.bundleid = 72
 service.factoryPid = org.ops4j.connectionfactory
 service.id = 106
 service.pid = org.ops4j.connectionfactory.109a6ced-1ace-4420-af45-daa886ddfd8f
 service.scope = singleton
 type = ibmmq
Provided by : 
 OPS4J Pax JMS Config (72)

Test the connection:

karaf@root()> feature:install -v jms
Adding features: jms/[4.2.0,4.2.0]
...

karaf@root()> jms:connectionfactories
JMS Connection Factory
──────────────────────
mq9

karaf@root()> jms:info -u app -p pax mq9                                                                                                                                                                                                   
Property │ Value
─────────┼────────────────────
product  │ IBM MQ JMS Provider
version  │ 8.0.0.0

karaf@root()> jms:send -u app -p pax mq9 DEV.QUEUE.1 "Hello IBM MQ 9 from PAX-JMS"    
                                                                                                                                                         
karaf@root()> jms:browse -u app -p pax mq9 DEV.QUEUE.1                                                                                                                                                                                         
Message ID                                          │ Content                     │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination          │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp
────────────────────────────────────────────────────┼─────────────────────────────┼─────────┼──────┼────────────────┼───────────────┼──────────────────────┼────────────┼──────────┼─────────────┼─────────┼──────────────────────────────
ID:414d5120504158514d2020202020202072cdf25a02006924 │ Hello IBM MQ 9 from PAX-JMS │ UTF-8   │      │                │ Persistent    │ queue:///DEV.QUEUE.1 │ Never      │ 4        │ false       │         │ Wed May 09 12:33:33 CEST 2018

We can check if the message was sent also from IBM MQ Explorer or from IBM MQ Web Console.

Summary of handled properties

Properties from configuration admin factory PID are passed to relevant org.ops4j.pax.jms.service.ConnectionFactoryFactory implementation.

ActiveMQ - org.ops4j.pax.jms.activemq.ActiveMQConnectionFactoryFactory

  • properties passed to org.apache.activemq.ActiveMQConnectionFactory.buildFromMap() method

Artemis - org.ops4j.pax.jms.artemis.ArtemisConnectionFactoryFactory

  • if protocol=amqp, properties are passed to org.apache.qpid.jms.util.PropertyUtil.setProperties() method to configure org.apache.qpid.jms.JmsConnectionFactory instance
  • otherwise, org.apache.activemq.artemis.utils.uri.BeanSupport.setData() is called for org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory instance

IBM MQ - org.ops4j.pax.jms.ibmmq.MQConnectionFactoryFactory

  • bean properties of com.ibm.mq.jms.MQConnectionFactory or com.ibm.mq.jms.MQXAConnectionFactory are handled

The properties that can't be set should be ignored - see PAXJMS-15 - Getting issue details... STATUS

Using console commands

Apache Karaf provides jms feature that includes shell commands in the jms:* scope. We already tried some of them to check manually configured connection factories, but there are also commands that hide the need to create Configuration Admin configurations.

We could register broker-specific connection factory using (starting with fresh instance of Karaf):

Install jms feature from Karaf and pax-jms-artemis from pax-jms:

karaf@root()> feature:repo-add mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features
Adding feature url mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features

karaf@root()> feature:repo-add mvn:org.apache.activemq/artemis-features/2.5.0/xml/features                             
Adding feature url mvn:org.apache.activemq/artemis-features/2.5.0/xml/features

karaf@root()> feature:install jms pax-jms-artemis 

karaf@root()> jms:connectionfactories                                                                                  
JMS Connection Factory
──────────────────────

karaf@root()> service:list javax.jms.ConnectionFactory # should be empty

karaf@root()> service:list org.ops4j.pax.jms.service.ConnectionFactoryFactory                                          
[org.ops4j.pax.jms.service.ConnectionFactoryFactory]
----------------------------------------------------
 service.bundleid = 75
 service.id = 94
 service.scope = singleton
 type = artemis
Provided by : 
 OPS4J Pax JMS Artemis Support (75)

Create and check Artemis connection factory:

karaf@root()> jms:create -t artemis -u pax -p pax --url tcp://localhost:61616 artemis                                  
karaf@root()> jms:connectionfactories
JMS Connection Factory
──────────────────────
jms/artemis

karaf@root()> jms:info -u pax -p pax jms/artemis                                                                       
Property │ Value
─────────┼─────────
product  │ ActiveMQ
version  │ 2.5.0

karaf@root()> config:list '(service.factoryPid=org.ops4j.connectionfactory)'                                           
----------------------------------------------------------------
Pid:            org.ops4j.connectionfactory.edd7ad54-0871-4d80-aa2b-bd9758835fb3
FactoryPid:     org.ops4j.connectionfactory
BundleLocation: mvn:org.ops4j.pax.jms/pax-jms-config/1.0.0
Properties:
   name = artemis
   osgi.jndi.service.name = jms/artemis
   password = pax
   service.factoryPid = org.ops4j.connectionfactory
   service.pid = org.ops4j.connectionfactory.edd7ad54-0871-4d80-aa2b-bd9758835fb3
   type = artemis
   url = tcp://localhost:61616
   user = pax


As we can see, org.ops4j.connectionfactory factory PID was created for us. However it’s not automatically stored in ${karaf.etc}, which is possible with config:update. There’s also no way to specify other properties (but we can add them later).

Using properties resolved from external storage

Properties can be stored outside the main configuration file and resolved by a marker placeholder. By default pax-jms does support FILE als build in marker to resolve content from an explicit file. We also support special use cases by implementing generic interface org.ops4j.pax.jms.config.ConfigLoader and exporting as a service (e.g. via OSGi DS).

@Component
public class CustomConfigLoader implements ConfigLoader {

    @Override
    public String getName() {
        return "CUSTOM";
    }

    @Override
    public String resolve(String key) {
        return key;
    }
}

Reserved keywords for name actually are FILE and ENC!


Usage:

felix.fileinstall.filename = ${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg
name = artemis
type = artemis
url = tcp://localhost:61616
user = pax
password = FILE(<filename>)

Using encrypted configuration values

We can use Jasypt to encrypt properties.

If there’s any org.jasypt.encryption.StringEncryptor service registered in OSGi with any alias service property, we can reference it in connection factory factory PID and use encrypted passwords. Here’s an example:

felix.fileinstall.filename = ${karaf.etc}/org.ops4j.connectionfactory-artemis.cfg
name = artemis
type = artemis
decryptor = my-jasypt-decryptor
url = tcp://localhost:61616
user = pax
password = ENC(<encrypted-password>)

The service filter used to find decryptor service is (&(objectClass=org.jasypt.encryption.StringEncryptor)(alias=<alias>)), where <alias> is the value of decryptor property from connection factory configuration factory PID.

Using connection pools

It’s time to present JMS connection/session pooling options.

In order to use XA recovery, pax-jms-pool-transx or pax-jms-pool-narayana should be used.

So far we’ve registered broker-specific connection factory (because connection factory itself is a factory for connections, org.ops4j.pax.jms.service.ConnectionFactoryFactory may be treated as meta factory) that should be able to produce 2 kinds of connection factories:

  • javax.jms.ConnectionFactory
  • javax.jms.XAConnectionFactory

pax-jms-pool-* bundles work smoothly with the above described org.ops4j.pax.jms.service.ConnectionFactoryFactory services. These bundles provide implementations of org.ops4j.pax.jms.service.PooledConnectionFactoryFactory that can be used to create pooled connection factory using set of properties and original org.ops4j.pax.jms.service.ConnectionFactoryFactory (in a kind of wrapper way).

public interface PooledConnectionFactoryFactory {

    ConnectionFactory create(ConnectionFactoryFactory cff, Map<String, Object> props);

}

Which bundles register pooled connection factory factories (o.o.p.j.p == org.ops4j.pax.jms.pool)?

BundlePooledConnectionFactoryFactorypool key
pax-jms-pool-pooledjmso.o.p.j.p.pooledjms.PooledJms(XA)PooledConnectionFactoryFactorypooledjms
pax-jms-pool-narayanao.o.p.j.p.narayana.PooledJms(XA)PooledConnectionFactoryFactorynarayana
pax-jms-pool-transxo.o.p.j.p.transx.Transx(XA)PooledConnectionFactoryFactorytransx

pax-jms-pool-narayana factory is called PooledJms(XA)PooledConnectionFactoryFactory because it is based on pooled-jms library - just adding integration with Narayana Transaction Manager in terms of XA Recovery.

The above bundles only install connection factory factories. Not the connection factory themselves. So again we need something that’ll actually call org.ops4j.pax.jms.service.PooledConnectionFactoryFactory.create() method.

pax-jms-pool-pooledjms

We’ll describe the integration with this pool for explanatory purposes. This pool uses https://github.com/messaginghub/pooled-jms project.

pax-jms-config bundle is tracking instances of org.ops4j.pax.jms.service.PooledConnectionFactoryFactory registered by one of pax-jms-pool-* bundles.

If factory configuration contains pool property, the ultimate connection factory registered by pax-jms-config bundle will be the broker-specific connection factory, but wrapped inside one of (if pool=pooledjms):

  • org.messaginghub.pooled.jms.JmsPoolConnectionFactory (xa=false)
  • org.messaginghub.pooled.jms.JmsPoolXAConnectionFactory (xa=true)

Besides pool property (and boolean xa property, which selects one of non-xa/xa connection factories), org.ops4j.connectionfactory factory PID may contain properties prefixed with pool..

For pooled-jms these prefixed properties are used (after removing the prefix) to configure instance of:

  • org.messaginghub.pooled.jms.JmsPoolConnectionFactory, or
  • org.messaginghub.pooled.jms.JmsPoolXAConnectionFactory

Here’s quite realistic configuration of pooled-jms pool (org.ops4j.connectionfactory-artemis factory PID) using convenient syntax with jms.-prefixed properties:

# configuration for pax-jms-config to choose and configure specific org.ops4j.pax.jms.service.ConnectionFactoryFactory
name = artemis
connectionFactoryType = ConnectionFactory
jms.url = tcp://localhost:61616
jms.user = pax
jms.password = pax
# org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory specific coniguration
jms.callTimeout = 12000
# ...

# hints for pax-jms-config to use selected org.ops4j.pax.jms.service.PooledConnectionFactoryFactory
pool = pooledjms
xa = false

# pooled-jms specific configuration of org.messaginghub.pooled.jms.JmsPoolConnectionFactory
pool.idleTimeout = 10
pool.maxConnections = 100
pool.blockIfSessionPoolIsFull = true
# ...

In the above configuration, pool and xa keys are hints (service filter properties) to choose one of registered org.ops4j.pax.jms.service.PooledConnectionFactoryFactory services. In case of pooled-jms it’s:

karaf@root()> feature:install pax-jms-pool-pooledjms   
                                                                
karaf@root()> bundle:services -p org.ops4j.pax.jms.pax-jms-pool-pooledjms                                              

OPS4J Pax JMS MessagingHub JMS Pool implementation (77) provides:
-----------------------------------------------------------------
objectClass = [org.ops4j.pax.jms.service.PooledConnectionFactoryFactory]
pool = pooledjms
service.bundleid = 77
service.id = 87
service.scope = singleton
xa = false
----
objectClass = [org.ops4j.pax.jms.service.PooledConnectionFactoryFactory]
pool = pooledjms
service.bundleid = 77
service.id = 97
service.scope = singleton
xa = true

Example

For completeness, here’s full example with connection pool configuration:

Install required features:

karaf@root()> feature:repo-add mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features
Adding feature url mvn:org.ops4j.pax.jms/pax-jms-features/1.0.0/xml/features

karaf@root()> feature:repo-add mvn:org.apache.activemq/artemis-features/2.5.0/xml/features
Adding feature url mvn:org.apache.activemq/artemis-features/2.5.0/xml/features

karaf@root()> feature:install -v pax-jms-pool-pooledjms pax-jms-artemis                                                
Adding features: pax-jms-artemis/[1.0.0,1.0.0],pax-jms-pool-pooledjms/[1.0.0,1.0.0]
...

Install jms feature

karaf@root()> feature:install jms

karaf@root()> service:list org.ops4j.pax.jms.service.ConnectionFactoryFactory                                          
[org.ops4j.pax.jms.service.ConnectionFactoryFactory]
----------------------------------------------------
 service.bundleid = 73
 service.id = 87
 service.scope = singleton
 type = artemis
Provided by : 
 OPS4J Pax JMS Artemis Support (73)

karaf@root()> service:list org.ops4j.pax.jms.service.PooledConnectionFactoryFactory                                    
[org.ops4j.pax.jms.service.PooledConnectionFactoryFactory]
----------------------------------------------------------
 pool = pooledjms
 service.bundleid = 75
 service.id = 97
 service.scope = singleton
 xa = false
Provided by : 
 OPS4J Pax JMS MessagingHub JMS Pool implementation (75)

[org.ops4j.pax.jms.service.PooledConnectionFactoryFactory]
----------------------------------------------------------
 pool = pooledjms
 service.bundleid = 75
 service.id = 102
 service.scope = singleton
 xa = true
Provided by : 
 OPS4J Pax JMS MessagingHub JMS Pool implementation (75)

Create factory configuration:

karaf@root()> config:edit --factory --alias artemis org.ops4j.connectionfactory
karaf@root()> config:property-set connectionFactoryType ConnectionFactory
karaf@root()> config:property-set name artemis
karaf@root()> config:property-set type artemis
karaf@root()> config:property-set protocol amqp # so we switch to org.apache.qpid.jms.JmsConnectionFactory
karaf@root()> config:property-set jms.url amqp://localhost:61616
karaf@root()> config:property-set jms.username pax
karaf@root()> config:property-set jms.password pax
karaf@root()> config:property-set pool pooledjms
karaf@root()> config:property-set xa false
karaf@root()> config:property-set pool.idleTimeout 10
karaf@root()> config:property-set pool.maxConnections 123
karaf@root()> config:property-set pool.blockIfSessionPoolIsFull true
karaf@root()> config:update

Check if pax-jms-config processed the configuration into javax.jms.ConnectionFactory service:

karaf@root()> service:list javax.jms.ConnectionFactory                                                                 
[javax.jms.ConnectionFactory]
-----------------------------
 connectionFactoryType = ConnectionFactory
 felix.fileinstall.filename = file:/data/servers/apache-karaf-4.2.0/etc/org.ops4j.connectionfactory-5033358905176596086.cfg
 jms.password = pax
 jms.url = amqp://localhost:61616
 jms.username = pax
 name = artemis
 osgi.jndi.service.name = artemis
 pax.jms.managed = true
 pool.blockIfSessionPoolIsFull = true
 pool.idleTimeout = 10
 pool.maxConnections = 123
 protocol = amqp
 service.bundleid = 74
 service.factoryPid = org.ops4j.connectionfactory
 service.id = 104
 service.pid = org.ops4j.connectionfactory.732ee8f2-cb21-4e57-b27d-f65d9d0d6296
 service.scope = singleton
 type = artemis
Provided by : 
 OPS4J Pax JMS Config (74)

Use the connection factory

karaf@root()> jms:connectionfactories                                                                                  
JMS Connection Factory
──────────────────────
artemis

karaf@root()> jms:info -u pax -p pax artemis                                                                           
Property │ Value
─────────┼────────
product  │ QpidJMS
version  │ 0.30.0

karaf@root()> jms:send -u pax -p pax artemis DEV.QUEUE.1 "Hello Artemis from PAX-JMS"                                  

karaf@root()> jms:browse -u pax -p pax artemis DEV.QUEUE.1                                                           
Message ID                                      │ Content                    │ Charset │ Type │ Correlation ID │ Delivery Mode │ Destination │ Expiration │ Priority │ Redelivered │ ReplyTo │ Timestamp
────────────────────────────────────────────────┼────────────────────────────┼─────────┼──────┼────────────────┼───────────────┼─────────────┼────────────┼──────────┼─────────────┼─────────┼──────────────────────────────
ID:39bd5de4-5ee0-45ef-887f-7ff00c53fa6a:2:1:1-1 │ Hello Artemis from PAX-JMS │ UTF-8   │      │                │ Persistent    │ DEV.QUEUE.1 │ Never      │ 4        │ false       │         │ Wed May 09 13:18:27 CEST 2018


pax-jms-pool-narayana

For clarification, pax-jms-pool-narayna does almost everything as pax-jms-pool-pooledjms - installs pooled-jms-specific org.ops4j.pax.jms.service.PooledConnectionFactoryFactory - both for XA and non-XA scenarios. The only difference is that in XA scenario we have additional integration point:

  • org.jboss.tm.XAResourceRecovery OSGi service is registered to be picked up by com.arjuna.ats.arjuna.recovery.RecoveryManager

pax-jms-pool-transx

The implementation of org.ops4j.pax.jms.service.PooledConnectionFactoryFactory services provided by this bundle is based on pax-transx-jms bundle, which creates javax.jms.ConnectionFactory pools using org.ops4j.pax.transx.jms.ManagedConnectionFactoryBuilder facility. This is JCA (Java™ Connector Architecture) based solution.

Deploying connection factories as artifacts

In deployment method, javax.jms.ConnectionFactory services are registered directly by application code - usually inside Blueprint container. Blueprint XML may be part of ordinary OSGi bundle, installable using mvn: URI and stored in Maven repository (local or remote). It’s much easier to version-control such bundles comparing to Configuration Admin configurations.

pax-jms-config version 1.0.0 adds a deployment method for connection factory configuration. Application developer registers javax.jms.(XA)ConnectionFactory service (usually using Bluerpint XML) and specifies service properties. Then pax-jms-config detects such registered broker-specific connection factory and (using service properties) wraps the service inside generic, non broker-specific connection pool.

For completeness, I’ll present three deployment methods using Blueprint XML.

Manual deployment of connection factories

In this method, we don’t need pax-jms-config at all. Application code is responsible for registration of both broker-specific and generic connection factory.

<!--
Broker-specific, non-pooling, non-enlisting javax.jms.XAConnectionFactory
-->
<bean id="artemis" class="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory">
    <argument value="tcp://localhost:61616" />
    <argument value="username" />
    <argument value="password" />
    <property name="callTimeout" value="2000" />
    <property name="initialConnectAttempts" value="3" />
</bean>

<!--
Service exported from pax-transx-tm-narayana bundle - or from any bundle that registers JTA Transaction Manager
-->
<reference id="tm" interface="javax.transaction.TransactionManager" />

<!--
Non broker-specific, generic, pooling, enlisting javax.jms.ConnectionFactory
-->
<bean id="pool" class="org.messaginghub.pooled.jms.JmsPoolXAConnectionFactory">
    <property name="connectionFactory" ref="artemis" />
    <property name="transactionManager" ref="tm" />
    <property name="maxConnections" value="10" />
    <property name="idleTimeout" value="10000" />
</bean>

<!--
Expose connection factory to use by application code (like Camel, Spring, ...)
-->
<service interface="javax.jms.ConnectionFactory" ref="pool">
    <service-properties>
        <!-- Giving connection factory a name using one of these properties makes identification easier in jms:connectionfactories: -->
        <entry key="osgi.jndi.service.name" value="jms/artemis" />
        <!--<entry key="name" value="jms/artemis" />-->
        <!-- Without any of the above, name will fallback to "service.id" -->
    </service-properties>
</service>

Here are the shell commands that show how it should be used:

karaf@root()> feature:install artemis-core-client artemis-jms-client aries-blueprint
karaf@root()> install -s mvn:org.apache.commons/commons-pool2/2.5.0
Bundle ID: 69
karaf@root()> install -s mvn:org.messaginghub/pooled-jms/0.3.0
Bundle ID: 70
karaf@root()> install -s blueprint:file:///path/to/artemis-manual.xml
Bundle ID: 89

karaf@root()> bundle:services -p 89
Bundle 89 provides:
-------------------
objectClass = [javax.jms.ConnectionFactory]
osgi.jndi.service.name = jms/artemis
osgi.service.blueprint.compname = pool
service.bundleid = 89
service.id = 126
service.scope = bundle
----
objectClass = [org.osgi.service.blueprint.container.BlueprintContainer]
osgi.blueprint.container.symbolicname = artemis-manual.xml
osgi.blueprint.container.version = 0.0.0
service.bundleid = 89
service.id = 127
service.scope = singleton

karaf@root()> feature:install jms

karaf@root()> jms:connectionfactories
JMS Connection Factory
──────────────────────
jms/artemis

karaf@root()> jms:info -u pax -p pax jms/artemis                                                                                                                                                                                               
Property │ Value
─────────┼─────────
product  │ ActiveMQ
version  │ 2.5.0

As shown in the above listing, blueprint bundle exports javax.jms.ConnectionFactory service which is generic, non broker-specific connection pool. The broker-specific javax.jms.XAConnectionFactory is not registered as OSGi service, because Blueprint XML doesn’t have explicit <service ref="artemis"> declaration.

Factory deployment of connection factories

In this method, we use pax-jms-config in a canonical way - following the way used by PAX JMS itself.

Here’s the Blueprint XML example:

<!--
A broker-specific org.ops4j.pax.jms.service.ConnectionFactoryFactory that can create (XA)ConnectionFactory
using properties. It's registered by pax-jms-* bundles
-->
<reference id="connectionFactoryFactory"
           interface="org.ops4j.pax.jms.service.ConnectionFactoryFactory"
           filter="(type=artemis)" />

<!--
Non broker-specific org.ops4j.pax.jms.service.PooledConnectionFactoryFactory that can create
pooled connection factories with the help of org.ops4j.pax.jms.service.ConnectionFactoryFactory

For example, pax-jms-pool-pooledjms bundle registers org.ops4j.pax.jms.service.PooledConnectionFactoryFactory
with these properties:
- pool = pooledjms
- xa = true|false (both are registered)
-->
<reference id="pooledConnectionFactoryFactory"
           interface="org.ops4j.pax.jms.service.PooledConnectionFactoryFactory"
           filter="(&(pool=pooledjms)(xa=true))" />

<!--
When using XA connection factories, javax.transaction.TransactionManager service is not needed here - it's used
internally by xa-aware pooledConnectionFactoryFactory
-->
<!--<reference id="tm" interface="javax.transaction.TransactionManager" />-->

<!--
Finally we can use both factories to expose pooled, xa-aware connection factory
-->
<bean id="pool" factory-ref="pooledConnectionFactoryFactory" factory-method="create">
    <argument ref="connectionFactoryFactory" />
    <argument>
        <props>
            <!--
                Properties needed by artemis-specific org.ops4j.pax.jms.service.ConnectionFactoryFactory
            -->
            <prop key="jms.url" value="tcp://localhost:61616" />
            <prop key="jms.callTimeout" value="2000" />
            <prop key="jms.initialConnectAttempts" value="3" />
            <!-- Properties needed by pooled-jms-specific org.ops4j.pax.jms.service.PooledConnectionFactoryFactory -->
            <prop key="pool.maxConnections" value="10" />
            <prop key="pool.idleTimeout" value="10000" />
        </props>
    </argument>
</bean>

<!--
Expose connection factory to use by application code (like Camel, Spring, ...)
-->
<service interface="javax.jms.ConnectionFactory" ref="pool">
    <service-properties>
        <!-- Giving connection factory a name using one of these properties makes identification easier in jms:connectionfactories: -->
        <entry key="osgi.jndi.service.name" value="jms/artemis" />
        <!--<entry key="name" value="jms/artemis" />-->
        <!-- Without any of the above, name will fallback to "service.id" -->
    </service-properties>
</service>

In the above example, we use factory beans that create connection factories using connection factory factories (…​). We don’t need explicit reference to javax.transaction.TransactionManager service, as this is tracked internally by XA-aware PooledConnectionFactoryFactory.

Blueprint bundle exports javax.jms.ConnectionFactory service which is generic, non broker-specific connection pool. The broker-specific javax.jms.XAConnectionFactory is not registered as OSGi service, because Blueprint XML doesn’t have explicit <service ref="artemis"> declaration.

Mixed deployment of connection factories

pax-jms-config 1.0.0 adds another way of wrapping broker-specific connection factories within pooling connection factories using service properties.

Here’s the Blueprint XML example:

<!--
Broker-specific, non-pooling, non-enlisting javax.jms.XAConnectionFactory
-->
<bean id="artemis" class="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory">
    <argument value="tcp://localhost:61616" />
    <argument value="username" />
    <argument value="password" />
    <property name="callTimeout" value="2000" />
    <property name="initialConnectAttempts" value="3" />
</bean>

<!--
Expose broker-specific connection factory with service properties
No need to expose pooling, enlisting, non broker-specific javax.jms.XAConnectionFactory - it'll be registered
automatically by pax-jms-config with the same properties as this <service>, but with higher service.ranking
-->
<service id="pool" ref="artemis" interface="javax.jms.XAConnectionFactory">
    <service-properties>
        <!-- "pool" key is needed for pax-jms-config to wrap broker-specific connection factory inside connection pool -->
        <entry key="pool" value="pooledjms" />
        <!-- <service>/@id attribute doesn't propagate, but name of the connection factory is required using one of: -->
        <entry key="osgi.jndi.service.name" value="jms/artemis" />
        <!-- or: -->
        <!--<entry key="name" value="jms/artemis" />-->
        <!-- Other properties, that normally by e.g., pax-jms-pool-pooledjms -->
        <entry key="pool.maxConnections" value="10" />
        <entry key="pool.idleTimeout" value="10000" />
    </service-properties>
</service>

In the above example, we manually register only broker-specific connection factory. pool=pooledjms service property is a hint for connection factory tracker managed by pax-jms-config bundle. Connection Factory services with this service property will be wrapped within pooling connection factory (in this example - pax-jms-pool-pooledjms).

This time, jms:connectionfactories shows only one service, but that’s becuase this command removes duplicate names.

javax.jms.XAConnectionFactory is registered from the Blueprint bundle and have pool=pooledjms property declared.

javax.jms.ConnectionFactory is registered from pax-jms-config bundle and:

  • doesn’t have pool=pooledjms property (it was removed when registering wrapper connection factory)
  • has service.ranking=1000 property, so it’s always preferred version when e.g., looking for connection factory by name
  • has pax.jms.managed=true property, so it’s not tried to be wrapped again
  • has pax.jms.service.id.ref=<ID> property, so we know what’s the original connection factory service that’s wrapped inside connection pool