High Quality JMS Messaging.

Introduction

The JNDI Swiftlet provides a JNDI implementation for a SwiftMQ router to store and retrieve JMS administered objects.

The registration takes place automatically by the JMS Swiftlet (connection factories), the Queue Manager Swiftlet (queues) and the Topic Manager Swiftlet (topics) as soon as these resources are defined.

Lookup Implementation prior to Release 7.0.0

SwiftMQ implements JNDI via a JMS TopicConnection. Each Context.lookup() is translated into a request message that is broadcasted via the topic "swiftmq.jndi". Every JNDI Swiftlet on each connected router receives this request and replies with the requested JNDI object in case the object with this name is registered at this router. The first received JNDI object will be returned from Context.lookup(), all other incoming replies will be ignored. Therefore, every JMS administered object on every router of a network is immediately available for lookup from everywhere as soon as a routing connection has been established.

Lookup Implementation of Release 7.0.0 onwards

SwiftMQ implements JNDI via a JMS Connection. Each Context.lookup() is translated into a request message that is send to queue "sys$jndi" of the connected router. The local JNDI Swiftlet receives this request and replies with the requested JNDI object in case the object with this name is registered at the local router. This guarantees that a lookup request is first served from the local JNDI Swiftlet. If the requested object is not found, the local JNDI Swiftlet forwards the request to topic "swiftmq.jndi" so all other JNDI Swiftlet can now serve the lookup request, if possible. See section above.

This different lookup behavior ensures that a lookup is always resolved from the local router first, if possible. It is not required anymore to have different JNDI aliases for a queue or a connection factory. For example, should always the JMS connection factory "QueueConnectionFactory" be used, regardless to which router the JMS client connects to, this can now be done. It was not possible with the behavior prior to 7.0.0, because here the lookup request was broadcasted to all routers and there was a chance to receive a lookup response from a remote router.

Implementation Notes

JNDI Aliases

There is the possibility to define any aliases via the JNDI Swiftlet configuration and to map these to registered JNDI objects. Through this, the JMS clients become independent of queue locations. For example, if a queue exists with name "mailout@router4" and an alias "mailqueue" is defined hereto which is then used by the JMS client during the JNDI lookup, the queue may be displaced to other routers without changing the JMS clients which access it.

Static Remote Queues

In principle, SwiftMQ's JNDI implementation is structured dynamically. Every local router got it's registrations, receives lookup requests and, if an object is saved by its name, gives this back to the client. Problems may arise if a remote router is disconnected, but a static route exists to this router. In this case, one may send to the router (this is stored until he is reconnected, store-and-forward), but the according queue object may not be requested by JNDI. That way, only the "createQueue()" method would rest at the client to address the remote queue. To solve this problem, static remote queues may be defined in a local JNDI Swiftlet. Static remote queues are queue objects, that means addresses, which are restored on a JNDI lookup.

InitialContextFactory Class Name

JNDI uses a factory pattern to create a vendor specific implementation of InitialContext. The name of the provider's implementation of this InitialContextFactory must be set in an environment table before creating an InitialContext object. The name of SwiftMQ's InitialContextFactory implementation is "com.swiftmq.jndi.InitialContextFactoryImpl".

JNDI Provider URL

SwiftMQ uses a JNDI provider URL to set up its internal connection properties. As specified in a previous section, a JNDI connection is backed up by a JMS connection. Hereby, any available JMS inbound listener can be used. For details please have a look at the JMS Swiftlet configuration.

The JNDI provider URL specifies the properties of the underlying JMS connection with the following format:

     smqp://[<user>[:<password>]@](<host>:<port>)|"intravm"[/[host2=<host2>][port2=<port2>]
      [reconnect=<boolean>][retrydelay=<long>][maxretries=<int>][type=<type>]
      [;timeout=<long>][;keepalive=<long>][debug=<boolean>]

Where

     smqp       ::=  Specifies the SwiftMQ Protocol
     <user>     ::=  Username. Default is 'anonymous'
     <password> ::=  User's password. Default is null.
     <host>     ::=  DNS hostname of the router or 1st HA instance
                     or the keyword "intravm" to connect intra-VM
     <port>     ::=  JMS listener port of the of the router or 1st HA instance
     host2      ::=  DNS hostname of the 2nd HA instance
     port2      ::=  JMS listener port of the 2nd HA instance
     reconnect  ::=  Specifies whether an automatic reconnect should be done
     retrydelay ::=  Amount in milliseconds to wait between reconnect retries
     maxretries ::=  Max. number of reconnect retries
     type       ::=  Class name of the socket factory used by this JMS listener.
                     In this release, com.swifmq.net.PlainSocketFactory and
                     com.swiftmq.net.JSSESocketFactory are available. Default is
                     com.swifmq.net.PlainSocketFactory. See JMS Swiftlet configuration for details.
     timeout    ::=  Specifies a timeout in milliseconds for lookups. If no JNDI object is
                     received within this time, the lookup throws a NamingException. Default is no timeout;
                     lookups are waiting until they receive the requested JNDI objects.
     keepalive  ::=  Specifies a keepalive interval in milliseconds. If this value is greater 0, a
                     timer is created on the client side to send keepalive messages to detect broken
                     JNDI connections. Default is 60000 (1 minute).
     debug      ::=  If true, debug information are printed to System.out. Good for testing.

Note: Attributes "host2" and "port2" are reserved when connection to a SwiftMQ HA Router.

Examples:

JNDI lookups should be performed via a JMS listener on host localhost, port 4001, as user anonymous. The JMS listener provides access via a com.swiftmq.net.PlainSocketFactory. No lookup timeout should be set:

    smqp://locahost:4001

JNDI lookups should be performed via a JMS listener on host www.swiftmq.com, port 4020, as user 'johnsmith', password 'ballaballa'. The JMS inbound listener provides access via a com.swiftmq.net.JSSESocketFactory. The lookup timeout should be set to 20 secs:

    smqp://johnsmith:ballaballa@www.swiftmq.com:4020/type=com.swiftmq.net.JSSESocketFactory;timeout=20000

Connects to host "jms1" at port 4001. If a connection lost is detected, it reconnects with a delay of 1 second between retries. It stops retries after 50 unsuccessful attempts. JNDI lookup timeout is set to 10 seconds.

    smqp://jms1:4001/timeout=10000;reconnect=true;retrydelay=1000;maxretries=50

Performing JNDI Lookups

To use JNDI, the following import statement has to be included into the application's import list:

    import javax.naming.*;

Furthermore, the JNDI 1.2.1 and the SwiftMQ classes must be accessible through the CLASSPATH.

Before creating an InitalContext object, two environment properties must be set. These are the names of the InitialContextFactory implementation and the SwiftMQ JNDI-Provider-URL:

    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,"com.swiftmq.jndi.InitialContextFactoryImpl");
    env.put(Context.PROVIDER_URL,"smqp://localhost:4001");

Then, the InitialContext is to be created:

    InitialContext ctx = new InitialContext(env);

Now, a JNDI connection has been established and lookups can be performed to fetch the appropriate administered objects:

    TopicConnectionFactory tcf = (TopicConnectionFactory)ctx.lookup("plainsocket@router3");
    Topic topic = (Topic)ctx.lookup("iit.projects.swiftmq");
    // etc pp

Bind/Rebind/Unbind

A JMS client that has created a TemporaryTopic or a TemporaryQueue can register this kind of objects within SwiftMQ's JNDI implementation. It is not permitted to register other types of objects. After registration, other JMS clients are able to lookup these objects under their registered names. This is a rare case, because the normal handling of this issue is done by the TopicRequestor resp. QueueRequestor helper classes. Here, the temporary destination is set in the message by the message producer as a JMSReplyTo header. The receiver/subscriber gets this destination and replies to it. In special cases, this procedure is not possible. For this cases, the destinations could be registered within JNDI.

To register a temporary destination, use Context.bind:

    TemporaryQueue tq = queueSession.createTemporaryQueue();
    ctx.bind("myTempQueue",tq);

Another client is able to lookup this object and send messages to it:

    TemporaryQueue tq = ctx.lookup("myTempQueue");
    QueueSender sender = queueSession.createQueueSender(tq);
    sender.send(msg);

A client can also change an existing registration with Context.rebind:

    ctx.rebind("myInboundQueue",tq);

And, of course, it can remove the registration with:

    ctx.unbind("myInboundQueue");

JNDI registrations of temporary destinations have a lifetime of the temporary destination itself. If the temporary destination will be deleted by the client, for example with tq.delete(), or the JMS connection that has created the temporary destination will be closed (which deletes implicitly their temporary destinations), all JNDI registrations of this destinations will also be deleted automatically. So, a client does not need to explicitly call unbind for his registrations.

Automatic Reconnect after Connection Lost

This is deprecated since SwiftMQ 6.0.0. Please specify the reconnect attributes directly within the SMQP-URL!

When a JNDI context has losts its connection, a java.naming.CommunicationException will be thrown and the client application has to try itself to reconnect by trying to establish a new JNDI context. SwiftMQ 5.0.0 enhances its InitialContext with reconnect capabilities which can be configured by the following properties to be added to the environment properties:

Property Name Default Value Description
swiftmq.jndi.reconnect "false" Switches reconnect on/off.
swiftmq.jndi.reconnect.debug "false" Switches reconnect debug on/off. If "true", debug output goes to System.out.
swiftmq.jndi.reconnect.max.retries "5" Specifies the max. number of retries before throwing a CommunicationException.
swiftmq.jndi.reconnect.retry.delay "2000" Specifies the delay in milliseconds between the retries.
java.naming.provider.url_<nr> - An alternative provider URL. This is optional. If none is specified, retry takes place on the "normal" URL, otherwise retries are performed round-robin.

The following example retries on the same URL but maximal 100 times with a retry interval of 10 seconds and debug mode on:

     Hashtable env = new Hashtable();
     env.put(Context.INITIAL_CONTEXT_FACTORY,"com.swiftmq.jndi.InitialContextFactoryImpl");
     env.put(Context.PROVIDER_URL,"smqp://localhost:4001/timeout=10000");
     env.put("swiftmq.jndi.reconnect","true");
     env.put("swiftmq.jndi.reconnect.debug","true");
     env.put("swiftmq.jndi.reconnect.max.retries","100");
     env.put("swiftmq.jndi.reconnect.retry.delay","10000");
     InitialContext ctx = new InitialContext(env);

The next example is the same as above, except it tries 2 other routers in a round-robin fashion during reconnect:

     Hashtable env = new Hashtable();
     env.put(Context.INITIAL_CONTEXT_FACTORY,"com.swiftmq.jndi.InitialContextFactoryImpl");
     env.put(Context.PROVIDER_URL,"smqp://localhost:4001/timeout=10000");
     env.put(Context.PROVIDER_URL+"_2","smqp://localhost:4002/timeout=10000");
     env.put(Context.PROVIDER_URL+"_3","smqp://localhost:4003/timeout=10000");
     env.put("swiftmq.jndi.reconnect","true");
     env.put("swiftmq.jndi.reconnect.debug","true");
     env.put("swiftmq.jndi.reconnect.max.retries","100");
     env.put("swiftmq.jndi.reconnect.retry.delay","10000");
     InitialContext ctx = new InitialContext(env);

Closing the Context

It is recommended to fetch all administered objects only once, at application startup. If all lookups have been processed orderly, the InitialContext object should be closed, because of the open JMS connection. Closing the InitialContext, the connection is dropped and all resources on client and server side are released. Otherwise the client does not terminate, because there are running threads. In other words, close the InitialContext object after your lookups:

    ctx.close();

JNDI Replication

The JNDI Swiftlet provides the opportunity to replicate its JNDI content into external JNDI servers as well. Several of those replications can be defined. Before such replications can be defined, the resp. JNDI implementation classes have to be into the system classpath of the router, because the JNDI uses the system class loader to load the InitialContext class.

Once a JNDI replication is configured, it can be enabled (default is disabled). All registered JNDI objects (including JNDI aliases) of this router are then replicated into the foreign JNDI tree below the given context (default is "java:/comp/env/jms"). To ensure the foreign JNDI is up and running, a JNDI replication uses a keepalive mechanism to check that. On each keepalive interval, configured by the attribute "keepalive-interval", the replication component performs a JNDI lookup on the name, configured by the attribute "keepalive-lookup-name". It doesn't matter which name you specify here, since a NameNotFoundException is taken as ok; the foreign JNDI is up. All other exceptions are taken as the foreign JNDI is down. The replication component then tries to reconnect. Once a connection can be re-established, the whole JNDI content is replicated again.

To replicate into a LDAP server, make sure that the attribute "name-prefix" contains the prefix "cn=". The prefix will be used during bind and unbind operations.

It is recommended to enable the "jndi" trace space in the Trace Swiftlet during the configuration to see what's going on during the replication.