JNDI Compatibility
SwiftMQ implements InitialContextFactory and Context of JNDI version 1.2.1.
Administered JMS Objects
The term Administered Objects in JMS terminology refers to - TopicConnectionFactory
- QueueConnectionFactory
- Queue
- Topic
All administered objects are automatically registered by SwiftMQ per JNDI, as soon as they are defined as resources. This is also valid in router networks. They are available through JNDI as soon as a new routing connection has been established.
The names of the administered objects are constructed generically. For example, if a JMS inbound listener with the name plainsocket on router1 is defined, an administered object with the name plainsocket@router1 is generated for this inbound listener. The JNDI object registered under this name could serve both as TopicConnectionFactory and QueueConnectionFactory.
An administered object that contains a Queue is registered with its fully qualified queue name, because queues are location dependent. For example, if there is a queue backendqueue on router3 defined, the full qualified queue name is backendqueue@router3.
Topics are location independent, so they are be registered with their topic name.
Through the JNDISwiftlet, one can define aliases on these generic names. Please refer to the configuration of the JNDISwiftlet.
How JNDI is implemented
SwiftMQ implements JNDI via a JMS TopicConnection. Each Context.lookup() is translated into a request message that will be send to the topic swiftmq.jndi. Every JNDISwiftlet 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.
There are several advantages concerning this implementation approach, because all features of a JMS connection such as access control, socket factories and compression are available also for JNDI connections. Furthermore, broadcasting a lookup request simply to a topic where JNDISwiftlets are listening and replying builds a dynamic JNDI repository without any static configuration.
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 and connection factories.
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.
The same applies to connection factories. If, for example, an alias "QueueConnectionFactory" is defined within a router network upon all routers to the local JMS listener "plainsocket@<router>" and if the router fails, the "QueueConnectionFactory" of another router is delivered automatically on a JNDI lookup. The connection now takes place by it and a simple client fail-over has been implemented.
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 JMSSwiftlet configuration.
Note for developers: SwiftMQ does not set an URL handler in the JVM URL handler hierarchy.
The JNDI provider URL specifies the properties of the underlying JMS connection with the following format:
smqp://[<user>[:<password>]@]<host>:<port>[/[type=<type>][;timeout=<long>][;compress=<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 <port> ::= JMS inbound listener port type ::= Class name of the socket factory used by this JMS inbound listener. In this release, com.swifmq.net.PlainSocketFactory and com.swiftmq.net.JSSESocketFactory are available. Default is com.swifmq.net.PlainSocketFactory. See JMSSwiftlet configuration for details. timeout ::= Specifies a timeout in milliseconds for lookups. If no JNDI object is received within this time, the lookup returns null. Default is no timeout; lookups are waiting until they receive the requested JNDI objects. compress ::= Specifies if the specific JMS connection has line compression. Default is no compression.
|
Examples:
JNDI lookups should be performed via a JMS inbound listener on host localhost, port 4001, as user anonymous. The JMS inbound listener provides access via a com.swiftmq.net.PlainSocketFactory with no line compression. No lookup timeout should be set:
JNDI lookups should be performed via a JMS inbound 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 with line compression. The lookup timeout should be set to 20 secs:
smqp://johnsmith:ballaballa@www.swiftmq.com:4020/type=com.swiftmq.net.JSSESocketFactory;timeout=20000;compress=true
|
Performing JNDI Lookup’s
To use JNDI, the following import statement has to be included into the application's import list:
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. For security reasons, 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, he can remove the registration with Context.unbind:
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.
Closing the JNDI Context
It is suggested 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:
How to integrate external JNDI Servers
SwiftMQ provides the opportunity to integrate an external JNDI server and, in addition, to register administered JMS objects there as well as their respective aliases. This is reasonable if JMS clients use the application server and its JNDI system and avoids a special coding if SwiftMQ is used as JMS server. In this case, you only need the JNDI context of the respective application server.
In the case that an external JNDI server is integrated, the respective SwiftMQ message router registers all objects and aliases registered in SwiftMQ's JNDI implementation additional in the external JNDI server. Thus, it is possible to perform a lookup on a connection factory both on SwiftMQ's JNDI implementation and on the external JNDI server. However, it is not possible to access objects by SwiftMQ's JNDI implementation which are registered via the external JNDI server. This, because SwiftMQ registers only in addition but does not perform an additional lookup.
To integrated an external JNDI server, a configuration file is used which contains the respective JNDI properties, necessary to create an InitialContext object from SwiftMQ. Two example configuration files exist in the 'conf' directory of the SwiftMQ distribution, each to WebLogic and to jBoss. Both are tested successfully with these systems.
The name of the configuration file is defined in the configuration of the JNDISwiftlet by the property 'swiftlet.sys$jndi.external.jndi.configfile'. This may be defined with the administrationstools SwiftMQ Explorer or CLI or in the property configuration file of the respective router.
Example:
swiftlet.sys$jndi.external.jndi.configfile=../../conf/jboss.properties
|
The default value of the property is empty, which means, no external JNDI server is used.
Furthermore, the SwiftMQ router, from which this configuration results, needs to have the respective classes of the external JNDI server in the CLASSPATH.
The configuration file contains the following properties:
java.naming.property.names
Contains an enumeration of property names, set in the environment hashtable which is used to create the InitialContext object of the external JNDI servers. Subsequent, the properties are to be defined with the respective values.
Example WebLogic:
java.naming.property.names=java.naming.factory.initial,java.naming.provider.url java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory java.naming.provider.url=t3://localhost:7001
|
Example jBoss:
java.naming.property.names=java.naming.factory.initial,java.naming.factory.url.pkgs,java.naming.provider.url java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming java.naming.provider.url=localhost
|
context
Contains the context in the JNDI namespace, within the SwiftMQ administered JMS objects are to be registered at the external JNDI server. Subject to the J2EE regulations, it is the context 'java:comp/env/jms' to the JMS objects. This is also the default value of this property if no value is specified. SwiftMQ automatically creates this context if it has not been created in the JNDI namespace of the external JNDI server.
Example WebLogic:
This property does not need to be defined concerning jBoss. Registrations now take place in the default J2EE context 'java:comp/env/jms'.
Should the registration take place at the highest namespace level, the property needs to be set 'empty'.
Example:
On the first setup of an external JNDI server integration it is advisable to enable the space 'kernel' in the TraceSwiftlet. In the 'trace' directory, subdirectory of the respective router, you may find now the respective trace output in the file 'jndi.trace' and you can analyse, if the integration is successful or not.