From a Java app I'm trying to browse messages from a mainframe IBM MQ queue (EBCDIC messages). I need to browse messages, not consume them. Here is the code:
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
// Set properties
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, host);
cf.setIntProperty(WMQConstants.WMQ_PORT, port);
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, channel);
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, qmgr);
cf.setIntProperty(WMQConstants.WMQ_RECEIVE_CONVERSION, WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);
QueueBrowser browser = context.createBrowser(context.createQueue("queue:///" + queueName + "?targetClient=1"));
Enumeration enumeration = browser.getEnumeration();
while (enumeration.hasMoreElements()) {
TextMessage messageInTheQueue = (TextMessage) enumeration.nextElement();
System.out.println(messageInTheQueue);
nbRecords++;
}
The result of System.out.println() looks like:
JMSMessage class: jms_text
JMSType: null
JMSDeliveryMode: 2
JMSMessageID: ID:c1d4d840d4d8e3c1e2f24040404040405e2432bd21aa1b02
JMSTimestamp: 1579537307450
JMSRedelivered: false
JMSXAppID:
JMSXDeliveryCount: 1
JMSXUserID:
JMS_IBM_Character_Set: IBM037
JMS_IBM_Encoding: 273
JMS_IBM_Format: MQSTR
JMS_IBM_MsgType: 8
JMS_IBM_PutApplType: 8
JMS_IBM_PutDate: 20200120
JMS_IBM_PutTime: 16214745
ÍÍÑÀ ...
I would like to convert this EBCDIC message ÍÍÑÀ ... to something readable (ASCII).
I tried to cast enumeration.nextElement() to JMSByteMessage but get this exception:
class com.ibm.msg.client.jms.internal.JmsTextMessageImpl cannot be cast to class com.ibm.jms.JMSBytesMessage
How could I do that?
Solution:
Use the MQ classes for Java instead of the MQ JMS classes for Java:
byte[] strData = new byte[theMessage.getMessageLength()];
theMessage.readFully(strData, 0, theMessage.getMessageLength());
Some example here: https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.dev.doc/q030840_.htm
Regarding the OP's original code, the following documentation states that receive conversion & CCSID are only valid for the MQDestination class: https://www.ibm.com/docs/en/ibm-mq/9.2?topic=reference-properties-mq-classes-jms-objects
Therefore this can be done with the IBM MQ Classes for JMS. You just have to do it in a more round-about fashion.
In order to make the QueueBrowser read EBCDIC formatted messages, you need to cast the JMS Queue to MQDestination, then set the receiveConversion & receiveCCSID, then when creating the browser, cast the MQDestination back to regular JMS Queue. Like so:
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
// Set properties
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, host);
cf.setIntProperty(WMQConstants.WMQ_PORT, port);
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, channel);
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, qmgr);
JMSContext context = cf.createContext();
MQDestination targetQueue = (MQDestination) context.createQueue("queue:///" + queueName)
targetQueue.setReceiveCCSID(WMQConstants.CCSID_UTF8);
targetQueue.setReceiveConversion(WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);
targetQueue.setMessageBodyStyle(WMQConstants.WMQ_MESSAGE_BODY_MQ);
QueueBrowser browser = context.createBrowser((Queue) targetQueue);
Enumeration enumeration = browser.getEnumeration();
while (enumeration.hasMoreElements()) {
TextMessage messageInTheQueue = (TextMessage) enumeration.nextElement();
System.out.println(messageInTheQueue);
nbRecords++;
}
You also don't need to use the JMS URI properties for setting the target client when doing it this way, as MQDestination has all the necessary getters/setters to modify loads of properties that would otherwise be done with a URI.
For more information on MQDestination, consult the following docs: https://www.ibm.com/docs/en/ibm-mq/9.2?topic=jms-mqdestination
Related
In order to connect any application with ibmmq 9.1, what are the steps we need to follow , how to connect ibmmq with any application.
There are many samples shipped with product that you can refer to develop applications in Java and .NET. One simple sample written in Java JMS that receives message from a queue.
private void receiveWithContext(){
JmsConnectionFactory cf = null;
JMSContext msgContext = null;
try {
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
cf = ff.createConnectionFactory();
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "QM");
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_BINDINGS);
msgContext = cf.createContext("youruserid","yourpassword",JMSContext.AUTO_ACKNOWLEDGE);
Queue jmsQ = msgContext.createQueue("Q1");
JMSConsumer msgCons = msgContext.createConsumer(jmsQ);
Message msg = msgCons.receiveNoWait();
}catch(JMSException jmsEx){
System.out.println(jmsEx);
}
}
I am upgrading my application from a regular java application to Spring boot application.
I am connecting with an external application, using IBM MQ.
At the old version i got an MQMessage object and used only the feedback field. like this:
MQQueue currQ = this.qMgr.accessQueue("myQueue",MQC.MQOO_INQUIRE + MQC.MQOO_INPUT_SHARED + MQC.MQOO_FAIL_IF_QUIESCING);
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.options = MQC.MQGMO_SYNCPOINT;
MQMessage mqMsg = new MQMessage();
currQ.get(mqMsg, gmo);
System.out.println("feedback: " + mqMsg.feedback);
In my new code, I am using JmsListener:
#JmsListener(destination = "myQueue", containerFactory = "jmsListenerContainerFactory")
public void myQueueListener(String message) {
System.out.println("feedback: " + ???);
}
How do i get the feedback field?
I have no control on the external application that sends the message.
The chapter JMS fields and properties with corresponding MQMD fields explains the header mapping. You have two options to access the headers (see also this answer), here is one of it:
#JmsListener(destination = "myQueue", containerFactory = "jmsListenerContainerFactory")
public void myQueueListener(String message, #Header("JMS_IBM_Feedback") Integer feedback) {
System.out.println("feedback: " + feedback);
}
I use spring boot 2.3.2 with ibm mq.
I use property file setup mq.
ibm.mq.queueManager=
ibm.mq.channel=
ibm.mq.connName
ibm.mq.user
ibm.mq.password
ibm.mq.useIBMCipherMappings=false
ibm.mq.userAuthenticationMQCSP=false
ibm.mq.sslCipherSuite=TLS_RSA_WITH_AES_128_CBC_SHA256
that works fine.
I need to create another factory to be able to connect to another channel. So I would like to create a similar one which is created by default but with different channel name.
So I created a config class
#EnableJms
#Configuration
public class JmsConfig {
#Bean
public MQQueueConnectionFactory jmsMQConnectionFactoryPayment() throws JMSException {
MQQueueConnectionFactory connectionFactory = new MQQueueConnectionFactory();
connectionFactory.setQueueManager("AD_TEST");
connectionFactory.setChannel("FROM.PAYMENTMNG");
connectionFactory.setConnectionNameList("wmqd1.int.test.com(1818),wmqd2.int.test.com(1818)");
connectionFactory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, false);
connectionFactory.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "TLS_RSA_WITH_AES_128_CBC_SHA256");
connectionFactory.setIntProperty(CommonConstants.WMQ_CONNECTION_MODE, CommonConstants.WMQ_CM_CLIENT);
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", String.valueOf(Boolean.FALSE));
connectionFactory.createConnection("123", "123");
return connectionFactory;
}
#Bean
JmsListenerContainerFactory<?> jmsContainerFactoryPayment() throws JMSException {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(jmsMQConnectionFactoryPayment());
return factory;
}
#Bean("payment")
JmsTemplate jmsTemplatePayment() throws JMSException {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(jmsMQConnectionFactoryPayment());
return template;
}
}
In a class I have
#JmsListener(destination="xxx", containerFactory="jmsContainerFactoryPayment"){
....
}
When I start application, I get
com.ibm.msg.client.jms.DetailedIllegalStateException: JMSWMQ0018: E Failed to connect to queue manager 'AD_TEST' with connection mode 'Client' and host name 'wmqd1.int.test.com(1818),wmqd2.int.test.com(1818)'.
at com.ibm.msg.client.wmq.common.internal.Reason.reasonToException(Reason.java:489) ~[com.ibm.mq.allclient-9.2.0.0.jar:9.2.0.0 - p920-L200710.DE]
at com.ibm.msg.client.wmq.common.internal.Reason.createException(Reason.java:215) ~[com.ibm.mq.allclient-9.2.0.0.jar:9.2.0.0 - p920-L200710.DE]
at com.ibm.msg.client.wmq.internal.WMQConnection.<init>(WMQConnection.java:450) ~[com.ibm.mq.allclient-9.2.0.0.jar:9.2.0.0 - p920-L200710.DE]
at com.ibm.msg.client.wmq.factories.WMQConnectionFactory.createV7ProviderConnection(WMQConnectionFactory.java:8475) ~[com.ibm.mq.allclient-9.2.0.0.jar:9.2.0.0
Caused by: com.ibm.mq.MQException: JMSCMQ0001: IBM MQ call failed with compcode '2' ('MQCC_FAILED') ; reason '2538' ('MQRC_HOST_NOT_AVAILABLE').
at com.ibm.msg.client.wmq.common.internal.Reason.createException(Reason.java:203) ~[com.ibm.mq.allclient-9.2.0.0.jar:9.2.0.0 - p920-L200710.DE]
... 51 common frames omitted
Seem like we can't have two queue manager with same host but different channel
I have configured Spring Boot with IBM MQ to test this. Starting with the sample provided in this IBM Messaging GitHub Repo, I added two additional listeners as follows.
First I added some additional properties to the application.properties file
my.mq.queueManager=QM2
my.mq.channel=DEV.APP.SVRCONN
my.mq.connName=localhost(1415)
my.mq.user=<your_user_name>
my.mq.password=<your_password>
I left Application.java unchanged, copied Listener.java to create ListenerTwo.java and ListenerThree.java. I then added a new ListenerBeanConfig.java class to the sample.
ListenerTwo.java was changed to bind to a new ConnectionFactory configuration listenerTwoFactory which is created later.
#JmsListener(destination = Application.qName, containerFactory = "listenerTwoFactory")
ListenerThree.java was changed to bind to a new listenerThreeFactory configuration and Queue
#JmsListener(destination = "DEV.QUEUE.2", containerFactory = "listenerThreeFactory")
The ListenerBeanConfig.java class declaration was annotated so that I can access my properties by adding Strings for each property e.g., queueManager, channel, connName etc. and providing setter methods for each of them e.g.,
#Configuration
#EnableJms
#ConfigurationProperties(prefix="my.mq")
public class ListenerBeanConfig {
String connName;
public void setConnName(String value) {
System.out.println("connName is set to: "+value);
connName = value;
}
I registered the two new Listener beans
#Bean
public ListenerTwo myListenerTwo() {
return new ListenerTwo();
}
#Bean
public ListenerThree myListenerThree() {
return new ListenerThree();
}
I then created the new connection factory configurations listenerTwoFactory and listenerThreeFactory
For listenerTwoFactory I used the JMS classes provided by com.ibm.mq.jms in the Spring Boot config
JmsConnectionFactory cf;
#Bean
public DefaultJmsListenerContainerFactory listenerTwoFactory() {
DefaultJmsListenerContainerFactory containerFactory = new DefaultJmsListenerContainerFactory();
try {
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
cf = ff.createConnectionFactory();
cf.setStringProperty(WMQConstants.WMQ_CONNECTION_NAME_LIST, connName);
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, channel);
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, queueManager);
cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Spring Boot ListenerTwo");
cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true);
cf.setStringProperty(WMQConstants.USERID, user);
cf.setStringProperty(WMQConstants.PASSWORD, password);
} catch (JMSException jmsex) {
System.out.println(jmsex);
}
containerFactory.setConnectionFactory(cf);
return containerFactory;
}
For the listenerThreeFactory I used the MQ JMS helper classes from com.ibm.mq.spring.boot.
#Bean
public DefaultJmsListenerContainerFactory listenerThreeFactory() {
MQConfigurationProperties myProps = new MQConfigurationProperties();
myProps.setUser(user);
myProps.setChannel(channel);
myProps.setConnName(connName);
myProps.setPassword(password);
myProps.setQueueManager(queueManager);
//No customizer
MQConnectionFactoryFactory mqcff = new MQConnectionFactoryFactory(myProps,null);
MQConnectionFactory mqcf = mqcff.createConnectionFactory(MQConnectionFactory.class);
DefaultJmsListenerContainerFactory containerFactory = new DefaultJmsListenerContainerFactory();
containerFactory.setConnectionFactory(mqcf);
return containerFactory;
}
Finally, I compiled and ran the new sample configuration. Using the IBM MQ Console for two IBM MQ queue manager docker instances, I put messages to QM1: DEV.QUEUE.1 and QM2: DEV.QUEUE.1, DEV.QUEUE.2. On the terminal see the following output.
========================================
Received message is: message 1
========================================
========================================
ListenerTwo received message is: message 2
========================================
========================================
ListenerThree received message is: message 3
========================================
Also tested with all three listeners connected to QM2 via a two different channels: DEV.APP.SVRCONN and DEV.APP.SVRCONN.TWO.
I am sure there are far more elegant ways to manage the additional properties.
I am trying to connection to a ibm mq queue through standalone server.
(i am using 7.0.3 ibmmq jar)
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, "dcc");
cf.setIntProperty(WMQConstants.WMQ_PORT, 14321);
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, "dfds");
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "dw");
cf.setStringProperty(WMQConstants.USERID, "ww");
cf.setStringProperty(WMQConstants.PASSWORD, "vw");
i have set all these properties in connection Factory.
Conenction is made successfully but i am unable to open queue. getting the following error.
**MQJE001: Completion Code '2', Reason '6114'.**
FAILED: Queueconnection
com.ibm.msg.client.jms.DetailedJMSException: JMSWMQ2008: Failed to open MQ queue 'US.0732931.NGEN.MANIFEST.LOADS'.
JMS attempted to perform an MQOPEN, but WebSphere MQ reported an error.
Use the linked exception to determine the cause of this error. Check that the specified queue and queue manager are defined correctly.
at com.ibm.msg.client.wmq.common.internal.Reason.reasonToException(Reason.java:585)
at com.ibm.msg.client.wmq.common.internal.Reason.createException(Reason.java:221)
You really don't provide enough information.
What connection factory are you using?
The error happens on the MQOPEN but you do NOT show the code for your createQueue method which is the important
Here's sample code:
private void putMessage()
{
JmsConnectionFactory cf = null;
Connection connection = null;
Session session = null;
Destination reqQ = null;
MessageProducer producer = null;
try
{
// Create a connection factory
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
cf = ff.createConnectionFactory();
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "MY_QMGR_NAME");
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, "MY_TEST_CHL");
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, "some_remote_server");
cf.setIntProperty(WMQConstants.WMQ_PORT, 1414);
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.USERID, "my_uid");
cf.setStringProperty(WMQConstants.PASSWORD, "my_pwd");
// Create JMS objects
connection = cf.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
/**
* Create destination to send requests
* - MQA1 is the queue manager name
* - TEST.Q1 is the queue name
*/
reqQ = session.createQueue("queue://MQA1/TEST.Q1");
// Create producer
producer = session.createProducer(reqQ);
// Create a message
Message myMsg = session.createTextMessage("This is a test message.");
// Send it
producer.send(myMsg);
}
catch(Exception ex)
{
System.err.println(ex.getLocalizedMessage());
}
finally
{
try
{
session.close();
}
catch (Exception ex)
{
System.err.println("session.close() : " + ex.getLocalizedMessage());
}
try
{
connection.close();
}
catch (Exception ex)
{
System.err.println("connection.close() : " + ex.getLocalizedMessage());
}
}
}
I'm trying to configure Jms and WebSphere using java and using Jboss 6.3 in remote system.But am getting ClassNotFoundException in creation of MQQueueConnection Class.Here I please fine code.
Actually M not getting proper steps that what to do,I took help from IBM Knowledge Center but that is not helpful for me.
Please anyone who knows about it guide me and for below code Which jar files is required?
try {
MQQueueConnectionFactory cf = new MQQueueConnectionFactory();
// Config
cf.setHostName("167.190.249.202");
cf.setPort(1422);
cf.setTransportType(WMQConstants.WMQ_CM_CLIENT);
cf.setQueueManager("QM.EMPIRE");
cf.setChannel("EMPIRE.CONN");
MQQueueConnection connection = (MQQueueConnection) cf.createQueueConnection();
MQQueueSession session = (MQQueueSession) connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
MQQueue queue = (MQQueue) session.createQueue("queue:///Q1");
MQQueueSender sender = (MQQueueSender) session.createSender(queue);
MQQueueReceiver receiver = (MQQueueReceiver) session.createReceiver(queue);
long uniqueNumber = System.currentTimeMillis() % 1000;
JMSTextMessage message = (JMSTextMessage) session.createTextMessage("SimplePTP "+ uniqueNumber);
// Start the connection
connection.start();
sender.send(message);
System.out.println("Sent message:\\n" + message);
JMSMessage receivedMessage = (JMSMessage) receiver.receive(10000);
System.out.println("\\nReceived message:\\n" + receivedMessage);
sender.close();
receiver.close();
session.close();
connection.close();
System.out.println("\\nSUCCESS\\n");
}
catch (JMSException jmsex) {
System.out.println(jmsex);
System.out.println("\\nFAILURE\\n");
}
catch (Exception ex) {
System.out.println(ex);
System.out.println("\\nFAILURE\\n");
}
}
}
It is far better to point your CLASSPATH to where the MQ JAR files are installed rather than copy the MQ JAR files (i.e. you won't get the 'ClassNotFoundException' error).
But if you do copy the MQ JAR files then for an MQ JMS application, you pretty much need all of them:
com.ibm.mq.jar
com.ibm.mq.commonservices.jar
com.ibm.mq.headers.jar
com.ibm.mq.jmqi.jar
com.ibm.mq.pcf.jar
com.ibm.mqjms.jar
connector.jar
fscontext.jar
jms.jar
jndi.jar
jta.jar
ldap.jar
providerutil.jar