"JMS" means Java Message Service, and the JMS Connector is a connector that can tap into message queues implemented using the JMS standard. We can learn more about JMS in Sun Microsystem’s JMS Tutorial, and read about the API in the JMS specification and API documentation.
The JMS Connector's functions and features are:
The JMS Connector provides access to JMS based systems such as IBM MQ Server or the bundled MQe. A partly-preconfigured version of this Connector exists under the name "IBM MQ Connector", where the JMS Server Type is hidden, and pre-set to "IBMMQ".
Refer to Specific topics to see what you might need to do to our IBM TDI installation to make the JMS Connector work.
The Connector enables communication of both native Entry objects and XML text to be passed using a Java™ Message Server product.
The JMS Connector supports JMS message properties. Each message received by the JMS Connector populates the conn object with properties from the JMS message (see the getProperty() and setProperty() methods of the entry class to access these). conn object properties are prefixed with jms. followed by the JMS message property name. The property holds the value from the JMS message. When sending a message the user can set properties which are then passed on to the JMS message sent. The JMS Connector scans the conn object for properties that starts with jms. and set the corresponding JMS message property from the conn property.
The conn object is only available in a few hooks. See "Conn object" in IBM TDI V7.1 Users Guide.
Everything sent and received by the JMS Connector is a JMS message. The JMS Connector converts the IBM TDI Entry object into a JMS message and vice versa. Each JMS message contains predefined JMS headers, user defined properties and some kind of body that is either text, a byte array or a serialized Java object.
There exists a method as part of the JMS Connector which can greatly facilitate communication with the JMS bus: acknowledge(). The method acknowledge() is used to explicitly acknowledge all the JMS session's consumed messages when Auto Acknowlege is unchecked. By invoking acknowledge() of the Connector, the Connector acknowledges all messages consumed by the session to which the message was delivered. Calls to acknowledge are ignored when Auto Acknowlege is checked.
Careful thought must be given to the acknowledgement of received messages. As described, the best approach is to not use Auto Acknowledge in the JMS Connector, but rather insert a Script Connector right after the JMS Connector in the AssemblyLine, invoking the acknowledge() method of the JMS Connector. This ensures that the window between the relevant message information in the system store being saved, and the JMS queue notification is as small as possible. If a failure occurs in this window, the message is received once more.
Conversely, relying on Auto Acknowledge creates a window that exists from the point at which the message is retrieved from the queue (and acknowledged), until the message contents mapped into the entry is secured in the system store. If a failure occurs in this window, the message is lost, which can be a greater problem.
There could be a problem when configuring the JMS Connector in the Config Editor when Auto Acknowledge is on, because as long as this is the case, when going through the process of schema discovery using either Schema->Connect->GetNext or Quick Discover from Input Map the message will be grabbed and consumed (that is, gone from the input queue). This may be an unintended side-effect. To avoid this, turn Auto Acknowledge off before Schema detection -- but remember to switch it back on again afterwards, if this is the desired behavior
When the JMS Connector sends messages to WebSphere MQ, it is capable of sending these messages in two different modes depending on the client which will read these messages:
By default the Connector sends the messages so that they are intended to be read by non-JMS clients. The major difference between these two modes is that when the messages are intended to be read by non-JMS clients, the JMS properties are ignored. Thus a subsequent lookup on these properties will not find a match.
In order to switch to the "intended to be read by JMS clients" mode, the "Specific Driver Attributes" parameter value must contain the following line (apart from any other attributes specified): mq_nonjms=false
The JMS environment that enables you to send different types of data on the JMS bus. This Connector recognizes three of those types. The three types are referred to as Text Message, Bytes Message and Object Message. The most open-minded strategy is to use Text Message (for example, jms.usetextmessages=true) so that applications other than IBM TDI can read messages generated by the JMS Connector.
When you communicate with other IBM TDI servers over a JMS bus the BytesMessage provides a very simple way to send an entire Entry object to the recipient. This is also particularly useful when the entry object contains special Java objects that are not easy to represent as text. Most Java objects provide a toString() method that returns the string representation of it but the opposite is very rare. Also, the toString() method does not always return very useful information. For example, the following is a string representation of a byte array:
"[B@<memory-address>"
A text message carries a body of text. The format of the text itself is undefined so it can be virtually anything. When you send or receive messages of this type the Connector does one of two things depending on whether we have specified a Parser:
var str = work.getString ("message"); task.logmsg ("Received the following text: " + str );
If you expect to receive text messages in various formats (XML, LDIF, CSV ...) leave the Parser parameter blank and make the guess yourself as to what format the text message is. When you know the format we can use the system.parseObject(parserName, data) syntax to do the parsing for you:
var str = work.getString ("message"); // code to determine format if ( isLDIF ) e = system.parseObject( "ibmdi.LDIF", str ); else if ( isCSV ) e = system.parseObject ( "ibmdi.CSV", str ); else e = system.parseObject ( "ibmdi.XML", str ); } // Dump parsed entry to logfile task.dumpEntry ( e );
The Use Textmessage flag determines whether the Connector must use this method when sending a message.
An object message is a message containing a serialized Java object. A serialized Java object is a Java object that has been converted into a byte stream in a specific format which makes it possible for the receiver to resurrect the object at the other end. Testing shows that this is fine as long as the Java class libraries are available to the JMS server in both ends. Typically, a java.lang.String object causes no problems but other Java objects might. For this reason, the JMS Connector does not generate object messages but is able to receive them. When you receive an object message the Connector returns two attributes:
var obj = work.getObject ("java.object"); obj.anyMethodDefinedForTheObject ();
You only receive these messages.
A bytes message is a message carrying an arbitrary array of bytes. The JMS Connector generates this type of message when the Use Textmessage flag is false. The Connector takes the provided entry and serialize it into a byte array and send the message as a bytes message. When receiving a bytes message, the Connector first attempts to deserialize the byte array into an Entry object. If that fails, the byte array is returned in the message attribute. You must access the byte array using the getObject method in your work or conn entry.
var ba = work.getObject ("message"); for ( i = 0; i < ba.length; i++) task.logmsg ( "Next byte: " + ba [ i ] );
This type of message is generated only ifUse Textmessage is false (not checked).
A message selector is a String that contains an expression. The syntax of the expression is based on a subset of the SQL92 conditional expression syntax. The message selector in the following example selects any message that has a NewsType property that is set to the value 'Sports' or 'Opinion':
NewsType = 'Sports' OR NewsType = 'Opinion'
The Connector supports Lookup mode where the user can search for matching messages in a JMS Queue (Topic (Pub/Sub) is not supported by Lookup mode).
The Link Criteria specifies the JMS headers and properties for selecting matching messages on a queue.
For the advanced link criteria conform to the Message Selection specification as described in the JMS specification (http://java.sun.com/products/jms). The JMS Connector reuses the SQL filter specification (JMS message selection is a subset of SQL92) to build the message selection string. Turn on debug mode to view the generated message filter string.
There are basically two ways to perform a Lookup:
Decide which to use by setting the Lookup Removes flag in the Connector configuration. For Topic connections the Lookup Removes flag does not apply as messages on topics are always removed when a subscriber receives it. However, the Lookup mode heeds the Durable Subscriber flag in which case the JMS server holds any messages sent on a topic when you are disconnected.
The JMS Connector works in the same way as other Connectors in that we can specify a maximum number of entries to return in your AssemblyLine settings. To ensure you retrieve a single message only during Lookup, specify Max duplicate entries returned = 1 in the AssemblyLine settings. Setting Max duplicate entries returned to 1 enables you to retrieve one matching entry at a time regardless of the number of matching messages in the JMS queue.
Since the JMS bus is asynchronous the JMS Connector provides parameters to determine when the Lookup must stop looking for messages. There are two parameters that tells the Connector how many times it queries the JMS queue and for how long it waits for new messages during the query. Specifying 10 for the retry count and 1000 for the timeout causes the Connector to query the JMS queue ten times each waiting 1 second for new messages. If no messages are received during this interval the Connector returns. If during a query the Connector receives a message, it continues to check for additional messages (this time without any timeout) until the queue returns no more messages or until the received message count reaches the Max duplicate entries returned limit defined by the AssemblyLine. The effect of this is that a Lookup operation only retrieves those messages that are available at the moment.
In this mode, on each AssemblyLine iteration the JMS Connector sends an entry to the JMS server. If a Topic is used the message is published and if a Queue is used the message is queued.
In this mode the Connector has two attribute maps, both Input and Output. When the AssemblyLine invokes the Connector, an Output map operation is performed, followed by an Input map operation. There is a method in the JMS Connector called queryReply() which uses the class QueueRequestor. The QueueRequestor constructor is given a non-transacted QueueSession and a destination Queue. It creates a TemporaryQueue for the responses and provides a request() method that sends the request message and waits for its reply.
A JMS message consists of headers, properties and the body. Headers are accessed differently than properties and were not available in previous versions. In this version we can specify how to deal with headers and properties.
JMS headers are predefined named values that are present in all messages (although the value might be null). The following is a list of JMS header names this Connector supports:
(String) The unique message ID. Note that this is not a required field and can be null.
Since the JMS provider might not use your provided message ID, the Connector sets a special property called $jms.messageid after sending a message. This is to insure that the message ID always is available to the user. To retrieve this value use conn.getProperty("$jms.messageid") in your After Add hook.
(Destination) The queue/topic the sender expects replies to. When receiving a message this value holds the provider specific Destination interface object and is typically an internal Queue or Topic object. When sending a message either reuse the incoming Destination object or set the value to a valid topic/queue name. If the value is NULL (for example, an attribute with no values) or the string "%this%" the Connector uses its own queue/topic as the value. The difference between this method and explicitly setting the queue/topic name is that we need not update the attribute assignment if you change your Connector configuration's queue/topic name.
There is one restriction in the current version which enables you to only request a reply to the same type of connection as you are currently connected to. This means that you cannot publish a message on a topic and request the reply to a queue and vice versa.
It is not mandatory to respond to this header so the receiver of the message can completely ignore this field without any form of punishment.
These headers are all set by the provider and might be acted upon by the JMS driver for outgoing messages. In the configuration screen we can specify that we want all headers returned as attributes or specify a list of those of interest. All headers are named using a prefix of jms.. Also note that JMS header names always start with the string JMS. This means that never use property names starting with jms.JMS as they can be interpreted as headers.
In previous versions of this Connector all JMS properties were copied between the Entry object and the JMS Message. In this release we can refine this behavior by telling the Connector to return all user defined properties as attributes or specify a list of properties of interest. All properties are prefixed with jms. to separate them from other attributes. If you leave the list of properties blank and uncheck the JMS Properties As Attributes flag, you get the same behavior as for previous versions. Both JMS headers and JMS properties can be set by the user. If we use the backwards compatible mode set the entry properties in the Before Add hook as in:
conn.setProperty ( "jms.MyProperty", "Some Value" );
If you either check the JMS Properties As Attributes flag or specify a list of properties, provide the JMS properties as attributes. One way to do that is to add attributes using the jms. prefix in your attribute map. For example, if you add jms.MyProperty attribute map it results in a JMS property named MyProperty.
The Connector name is JMS Pub/Sub Connector, and it needs the following parameters:
The JMS Connector will support the IPv6 protocol if the JMS Server you connect to supports IPv6. IBM MQSeries 5.3 does not.
QUEUE_FACTORY_NAME=primaryQCF, or
TOPIC_FACTORY_NAME=primaryTCF
For more details on the structure of this parameter's JavaScript code as well as on the environment in which it executes, please see the section labeled "JMS Script driver" in the section about the System Queue in the IBM TDI V7.1 Installation and Administrator Guide and the System Queue Connector .
only a few headers can be set, and setting them does not mean the JMS provider ever uses them.
We can set the Max duplicate entries returned parameter in your AssemblyLine Configuration settings to prevent Lookup from returning more than one entry. If false, messages are returned as usual, but they are not removed from the queue.
A Parser can be selected form the Parser... pane; once in this pane, choose a parser by clicking the bottom-right Inheritance button. If a Parser is specified, a JMS Text message is parsed using this Parser. This Parser works with messages that are received by the JMS Connector, and is used to generate a text message when JMS Connector sends a message.
Go to the TDI_install_dir/examples/SoniqMQ directory of the IBM TDI installation.
TDI 7.1 comes with an example of a JMS script driver for Sonic MQ. This sample demonstrates how the TDI JMS components (JMS Connector, System Queue) can use the SonicMQ server as a JMS provider.
In directory TDI_install_dir/examples/was_jms_ScriptDriver you will find an example that demonstrates how to use the WebSphere Default JMS provider with the JMS Connector and the JMS Script Driver.
The configuration of external JMS systems which this Connector accesses is not specific to this Connector. Any external JMS system which this Connector accesses must be configured as it would be configured for any other JMS client.
WebSphere MQ: When IBM WebSphere MQ is used as a JMS provider the following jar files have to be taken from the WebSphere MQ installation, and placed under the TDI_install_dir\jars\3rdparty\IBM folder:
Enabling SSL
The SSL (Secure Socket Layer) protocol enables secure communications with MQ queue managers. In order to enable it, adjustments must be made to the MQ server as well as the JMS Connectors in your IBM TDI configuration. The steps below explain a sample setup.
Start iKeyman and select Key database file -> New. The "Key database type" must be CMS. We can choose the name and location of the file, but keep in mind that they must be set later in the queue manager's Key repository attribute. Check the Stash the password to a file? option and specify a password (it is used to access the file).
We can request a certificate from a Certification Authority (CA), but for the purposes of this example we'll use a self-signed certificate. Select Create -> New self-signed certificate and complete the form. The "Key label" attribute value must be in the form <ibmwebspheremq<aQueueManagerNameinLowerCase>> (for examplem "ibmwebspheremqmyqueuemanager").
Use the Extract certificate button, specify a name, location and data type and click OK.
Select your queue manager -> Properties -> SSL and modify the value of the "Key repository" attribute. The value must be the location and name of the key database file from step 1 but without the .kdb extension.
Certificates contain the distinguished name of the owner of the certificate. We can optionally configure the channel to accept only certificates with attributes in the distinguished name of the owner that match given values. To do this, select the Accept only certificates with Distinguished Names matching these values check box.
When another party initiates an SSL-enabled connection to a queue manager, the queue manager must send its personal certificate to the initiating party as proof of identity. We can also optionally configure the queue manager's channel so that the queue manager refuses the connection if the initiating party does not send its own personal certificate. To do this, on the SSL page of the Channel properties dialog, select Required from the Authentication of parties initiating connections list. For this example, you won't need this additional check, so select Optional.
For this operation use iKeyman again.
When an SSL connection is made, the queue manager will send its certificate as part of the initial handshake and the IBM TDI truststore will be checked in order to validate the received certificate. If it is not validated the connection will be terminated.
We can either edit the existing truststore file testServer.jks or create new Java keystore with the IBM Key Management tool. After that select the Signer certificates option from the combo box and click Add. Browse to the location you saved the extracted certificate from step 3 and select it. When you are prompted for a label use the same as in point 2 (ibmwebspheremqmyqueuemanager).
If you chose Required for the Authentication of parties initiating connections option, we will need to create our own personal self-signed certificate in the IBM TDI keystore and add it to the queue manager's key database file as signer certificate. The steps are identical with those specified above. This new certificate will be sent by our connector to the queue manager as part of the SSL handshake and if not present will result in termination of the connection.
As stated above, this is not needed if you chose Authentication of parties initiating connections -> Optional.
If we created new keystores or changed the location of the existing ones, this must be covered in solution.properties. For example:
javax.net.ssl.trustStore=C:\\Program Files\\IBM\\TDI\\V7.0\\jmsTrustStore javax.net.ssl.trustStorePassword= javax.net.ssl.trustStoreType=jks javax.net.ssl.keyStore=C:\\Program Files\\IBM\\WebSphere MQ\\Java\\bin\\jmsKeyStore javax.net.ssl.keyStorePassword=changeit javax.net.ssl.keyStoreType=jks
These modifications should be made prior to starting IBM TDI.
If you uncheck the Use SSL Connection checkbox the following fields will be retained in the saved configuration, but not used in subsequent non-SSL connections:
When the Use SSL Connection checkbox is NOT checked, the value specified for Server Channel will be used.
Considerations for Character encoding with IBM WebSphere MQ and the JMS Connector
In case of multiple IBM WebSphere MQ servers residing on different platforms some problems related to mismatched character sets may occur. Here are some key points to consider when resolving such issues:
For a solution to a concrete scenario and workaround refer to section Troubleshooting.
For more information about data conversion in IBM WebSphere MQ we can use the following Web sources:
We have to unpack the following jars file from this file:
Notes:
"%TDI_JAVA_PROGRAM%" -classpath "%TDI_HOME_DIR%\jars\3rdparty\IBM\ibmjms.jar; C:\MB_jars\com.ibm.msg.client.osgi.jms_1.0.0.0.jar;C:\MB_jars\com.ibm.msg.client.osgi.nls_1.0.0.0.jar; C:\MB_jars\com.ibm.msg.client.commonservices.jar;C:\MB_jars\com.ibm.msg.client.jms.jar; C:\MB_jars\com.ibm.msg.client.provider.jar;C:\MB_jars\com.ibm.msg.client.jms.internal.jar; C:\MB_jars\com.ibm.micro.client.nl_3.0.0.1-20081111.jar;C:\MB_jars\com.ibm.micro.client_3.0.0.1-20081111.jar; fC:\MB_jars\com.ibm.micro.utils.extended_3.0.0.1-20081111.jar;C:\MB_jars\com.ibm.micro.utils.nl_3.0.0.1-20081111.jar; C:\MB_jars\com.ibm.micro.utils_3.0.0.1-20081111.jar;C:\MB_jars\com.ibm.mqttclient.jms.nl_3.0.0.1-20081111.jar; C:\MB_jars\com.ibm.mqttclient.jms_3.0.0.1-20081111.jar;C:\MB_jars\com.ibm.mqttclient.nl_3.0.0.1-20081111.jar; C:\MB_jars\com.ibm.mqttclient_3.0.0.1-20081111.jar; %TDI_HOME_DIR%\IDILoader.jar" %ENV_VARIABLES% com.ibm.di.loader.IDILoader com.ibm.di.server.RS %*
The above list (which is one long line, broken up for visibility reasons) of .jar files might be different for the version of Microbroker you are using. Please consult the Microbroker documentation for the list of .jar files needed.
In case of systems containing two or more IBM WebSphere MQ servers exchanging messages, residing on different platforms the transmitted messages may be received corrupted.
For example consider the following scenario with two MQ servers - one MQ server on a z/OS platform sending messages to another MQ server on the Linux platform. If the received messages from the Linux MQ server are incorrect this might be due to a character set conversions since the default character set of the z/OS and Linux platforms are different. Here are some possible solutions when dealing with such an issue (in descending order, from most to least preferable):
ret.value = new java.lang.String(conn.getString("message").getBytes(z/OS_charset), Linux_charset);
This workaround is only applicable for the described scenario. In systems with more than two MQ servers a more complex decoding of the messages may be needed.