RFE – IBM MQ support for AdoptOpenJDK

Please review and vote for this RFE if you think it’s a good idea. The link below will take you directly there.

Headline:
Add support for AdoptOpenJDK in IBM MQ

URL to review the RFE and Vote for it if you like:
http://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=133991

Description:
Currently, IBM MQ supports 2 JDKs/JREs: IBM and Oracle. It would be extremely useful if IBM MQ supported a 3rd JDK/JRE called AdoptOpenJDK. IBM is a backer of AdoptOpenJDK, so it would seem logical that IBM MQ supports it.

Use case:
For those customers who want to move from using Oracle JDK/JRE to another option besides IBM’s JDK/JRE.

Business justification:
Starting in 2019, Oracle has implemented high monthly fees for the use of the Oracle JDK/JRE whereas previously the usage of the Oracle JDK/JRE was free.

Regards,
Roger Lacroix
Capitalware Inc.

IBM MQ, Java, JMS, Linux, Programming, Unix, Windows Comments Off on RFE – IBM MQ support for AdoptOpenJDK

IBM MQ and OpenJDKs

On Monday, Peter Potkay asked a simple question on the MQ List Server:

Does IBM support any version of OpenJDK, from any vendor, for use with IBM MQ Clients, specifically the Resource Adapter?

Tim McCormick of IBM responded with:

The full list of supported JREs are listed in the MQ system requirements here: https://www.ibm.com/support/docview.wss?uid=swg27006467

Specific link for 9.1 LTS: https://www.ibm.com/software/reports/compatibility/clarity-reports/report/html/softwareReqsForProduct?deliverableId=B560B760819A11E6B5854315721876AE&duComponentIds=A006

At this time we only support IBM and Oracle implementations.

Now that is the official line from IBM because they have not gone through the selection and testing of an OpenJDK implementation but since Oracle JDK and OpenJDK are 99% the same, I think IBM may give companies some leeway.

Here was my response to Peter’s question (and yes, you can say, Roger tell us how you REALLY feel).

Your question, although a clear answer should be simple, there are so many moving parts, you just end up banging your head against the wall.

Right off the top, I would like to thank Oracle for their greed which created this problem!!!!!!

Sun Microsystem said 13 years ago that they would open source Java and it would be free. When Oracle purchased Sun Microsystem, they said they would honor Sun’s promise to the Java community. Well, that last about 8 years. Last Summer, Oracle said f-u to the world and said if you want to use Oracle’s JRE and/or JDK, you need to pay (for both desktops and servers). Period. No exceptions.

Now Oracle’s JRE & JDK are 99% the same as OpenJDK. I haven’t found any differences between the 2 JDK’s (or JREs) but I write desktop applications and not Java back-end servers. See here: https://jaxenter.com/oracle-jdk-builds-openjdk-builds-difference-149318.html

I switched to AdoptOpenJDK. The primary reason I switched to it is because IBM is behind it!!!

A Java Enterprise Edition (Java EE) is built on top of a JDK. The current version of Java EE is 8. For a Java server to be considered to be a Java EE server, it has to implement a bunch of specifications. A Java server can be Java EE certified for 1 specification or all specifications for Java EE. If you go to wiki page on Java EE and scroll down to “Certified referencing runtimes”, you will see that JBoss is fully certified for Java EE 8 (and Java EE 7 too).

And now back to the “MQ Resource Adapter”. IBM’s web page on MQ Resource Adapter, says that the Java server you use must be certified for JCA v1.7 and JMS 2.0. For MQ v8 and v9, the Java server must be Java EE 7 certified.

So, as long as the version of the JBoss server you are running is Java EE 7 or 8 certified and you are using OpenJDK 8 or higher (which is the same as Oracle JDK 8 or higher), then you should be on a supported footing.

So, that’s my 2 cents.

Oh yeah, Oracle, here’s another 2 cents, you are a greedy, greedy, greedy, ………………. company.

Here is more that I did not put in my response to Peter’s question, Oracle not only screwed over Java users/customers but I know of 1 company that was forced to close!!!!

Excelsior LLC is (was) a 20 year old company that created and sold Excelsior Jet. Excelsior Jet is a compiler that compiles Java code to native code (executable). Excelsior LLC officially licensed the JDK/JRE from Oracle. Excelsior Jet was available for Windows, Linux, macOS and ARM (Linux & Windows).

Last year, Oracle changed the licensing of the Oracle JDK & JRE that would take effect in 2019. Monthly fee of $25.00 per server processor and $2.50 per desktop. Greedy, Greedy, greedy!!!

Now, if Excelsior LLC wanted to keep using the licensed Oracle JDK/JRE code then they would need to collect those monthly fees from the end-customers which are really the customers of their customers. i.e. Capitalware being a customer of Excelsior Jet would have to collect those monthly fees from all of our customers and give that money to Excelsior LLC, who then would give it to Oracle. What a lovely process.

Knowing how greedy Oracle is, I would even guess that they probably increased the licensing fees that Excelsior LLC had to pay them. Note: That is only a guess on my part but probably a reasonable guess.

Last October 31, 2018, announced a beta of Excelsior Jet 15.3 (officially released in November 2018) and the switchover to OpenJDK. That was great news, now Excelsior LLC customers (i.e. Capitalware) wouldn’t have to worry about chasing our customers for Oracle’s new licensing fees.

But the good news was short lived. On May 15, 2019, I received an email from Excelsior LLC saying that they were exiting the JVM business and shutting down.

There is a lot of speculation on Reddit, forums, etc. on why Excelsior LLC decided to shutdown. Most of it centers on Oracle’s GraalVM. While it could be the case, I seriously doubt it for 2 reasons:

  • Before GraalVM there was GCJ (GNU Compiler for Java). Excelsior LLC survived and thrived when GCJ existed, so Excelsior LLC should do the same with the existence of GraalVM.
  • Follow the money or more specifically, follow the licensing!!

    What do I mean by “follow the licensing”? Before Excelsior Jet 15.3, Excelsior LLC licensed the Oracle JDK/JRE from Oracle (Sun Microsystems before that). Last fall, Excelsior Jet switched to OpenJDK for Excelsior Jet 15.3. OpenJDK is licensed under GPL with the classpath exception. So, if Excelsior LLC wanted to stay on the right side of the law, they would have to publish their code when OpenJDK was included in Excelsior Jet 15.3 but they did not.

    So, I’m guessing that someone or company (probably Oracle) served Excelsior LLC with a legal notice that they are in violation of the terms of GPL.

    I do not know why Excelsior LLC did not just simply go the open source subscription route and publish their code. Maybe they were using proprietary code that could not be open sourced or just did not like the concept, I do not know. All I know is that this REALLY sucks!!!! I’ve been using Excelsior Jet for more than 7 years and have been a very happy user.

    The Laurel & Hardy quote is running through my head about Oracle:

    Well, here’s another nice mess you’ve gotten me into.

    In case I didn’t mention it before, Oracle is a greedy, greedy, greedy, …… company.

    Finally, I’ll check IBM MQ RFEs and if nobody has opened one for the support of OpenJDK then I’ll open it and request that AdoptOpenJDK be supported, since IBM is a backer of it.

    Regards,
    Roger Lacroix
    Capitalware Inc.

  • IBM MQ, Java, Linux, macOS (Mac OS X), Programming, Unix, Windows 1 Comment

    Oh Error Messages, Why Can’t You Say What You Mean?

    Talk about spending several days chasing my tail. 🙁

    I have a customer who build a brand new Windows 2016 Server and install IBM MQ v9.1 then applied Fix Pack 2. So, they were at MQ v9.1.0.2.

    Next, they installed MQAUSX and applied their generic definitions to it. So far, so good.

    But when they went to do a test, the channel was closed and the following error messages were found in the queue manager’s log file:

    ----- amqrimna.c : 866 --------------------------------------------------------
    6/10/2019 10:43:05 - Process(2156.3) User(MUSR_MQADMIN) Program(amqrmppa.exe)
                          Host(SERVER001) Installation(Installation1)
                          VRMF(9.1.0.2) QMgr(MQA1)
                          Time(2019-06-10T15:43:05.208Z)
                          ArithInsert1(24948) ArithInsert2(126)
                          CommentInsert1(d:\Capitalware\MQAUSX\mqausx.dll)
                          CommentInsert2(The specified module could not be found.)
                          CommentInsert3(64)
    
    AMQ6174I: The library 'd:\Capitalware\MQAUSX\mqausx.dll' was not found.
    
    EXPLANATION:
    The dynamically loadable library 'd:\Capitalware\MQAUSX\mqausx.dll' was not
    found. Possible reasons for the error:
    (a) Library is not present in the specified path.
    (b) Library is present but the architecture of the library does not match the
      process's architecture which is '64' bit.
    (c) Library is present but it has a dependency on other libraries which are not
      present in the same directory.
    ACTION:
    Check that the file exists and is either fully qualified or is in the
    appropriate directory. Check the architecture of the library and process match.
    Also check if the library has dependency on any other libraries.
    ----- amqxufnn.c : 706 --------------------------------------------------------
    6/10/2019 10:43:05 - Process(2156.3) User(MUSR_MQADMIN) Program(amqrmppa.exe)
                          Host(SERVER001) Installation(Installation1)
                          VRMF(9.1.0.2) QMgr(MQA1)
                          Time(2019-06-10T15:43:05.210Z)
                          ArithInsert1(24948)
                          CommentInsert1(TEST.CHL)
                          CommentInsert2(d:\Capitalware\MQAUSX\mqausx(SecExit))
                          CommentInsert3(64)
    
    AMQ9535E: User exit not valid.
    
    EXPLANATION:
    Channel program 'TEST.CHL' ended because user exit
    'd:\Capitalware\MQAUSX\mqausx(SecExit)' is not valid.
    Architecture of the exit library does not match the process's architecture
      which is '64' bit.
    ACTION:
    Ensure that the user exit is specified correctly in the channel definition, and
    that the user exit program is correct and available.

    The first error message was AMQ6174I which says “not found”. So, I worked with the customer to make sure the MQAUSX install directory and the SCYEXIT parameter for channel were correct and they were (directory listings, screenshots, etc.). So, that was a red herring.

    The second error message was AMQ9535E which says the “user exit is not valid”. Had the customer triple check what they had installed and used is32or64 program to verify that indeed they were using the 64-bit release of MQAUSX. Again, another red herring.

    I already had IBM MQ v9.1 installed on a 64-bit Windows Server, so I applied Fix Pack 2 and installed MQAUSX 64-bit on the D: drive, just as the customer did. Hence, I was at the same MQ level and MQAUSX setup as the customer’s queue manager and MQAUSX installation. Everything worked perfectly for me. 🙁

    We spent so much time going over everything with a fine-toothed comb but nothing was incorrect. Just banging my head against the wall.

    The customer asked if they require any Redistributable packages, since it is a brand new server, it doesn’t have any installed. I said if you don’t have “Microsoft Visual C++ 2010 Redistributable x64” installed then yes download and install it. After it was installed and the server was rebooted, everything worked as expected.

    Sometimes, I really hate Windows. I don’t know if this was MQ misinterpreting the Windows error message or Windows returning a bad error message to MQ. But it sure wasted a lot of my time. If this was an MQ misinterpreting the error message, then IBM please update your code to better reflect the “actual” issue.

    I have had a similar issues on Linux/Unix. On Linux/Unix, when an MQ exit (aka shared library) has a dependency on another shared library, the error message that is outputted to the queue manager’s error log file actually has useful information about the issue. It does not send you off on wild goose chases that have nothing to do with the issue.

    Regards,
    Roger Lacroix
    Capitalware Inc.

    C, Capitalware, IBM MQ, MQ Authenticate User Security Exit, Windows 3 Comments

    Windows: Is the exe or DLL 32-bit or 64-bit?

    Every so often, I have a customer send me an email with a IBM MQ message like the following:

    Channel program ‘TEST.CHL’ ended because user exit ‘C:\Capitalware\MQAUSX\mqausx(SecExit)’ is not valid.
    Architecture of the exit library does not match the process’s architecture which is ’64’ bit.

    MQAUSX is just an example, as it could happen with any other Capitalware product.

    For Windows, MQAUSX has 2 installers:

  • mqausx-server-setup.exe for 32-bit version of MQAUSX
  • mqausx-server-64-setup-MQv8.exe for 64-bit version of MQAUSX
  • It is easy for someone to select and run the wrong installer. Or sometimes, a user will simply copy the DLLs from one server to another server and not realize that the platform architecture (32 vs 64-bit) is different. If the user does then MQ will output the above error message.

    Windows does not include a program like “file” for Linux/Unix. Yes, there are several options that users can install:

  • “dumpbin” included with Visual Studio
  • Dependency Walker
  • Process Explorer
  • But sometimes you just want a simple command line tool. Hence, I decided to create a Windows command line program called is32or64 that will take file name(s), either executable (“exe”) or DLL, as parameter(s) and output if the named file is 32-bit or 64-bit.

    You can download is32or64 from here. The download archive contains 3 files:

  • bin32\is32or64.exe (32-bit release)
  • bin64\is32or64.exe (64-bit release)
  • ReadMe.txt
  • Example:

    C:\>is32or64.exe C:\Windows\regedit.exe C:\Windows\difxapi.dll
    C:\Windows\regedit.exe: 64-bit
    C:\Windows\difxapi.dll: 64-bit

    Enjoy.
    Regards,
    Roger Lacroix
    Capitalware Inc.

    Capitalware, Licensed As Free, MQ Authenticate User Security Exit, Operating Systems, Windows Comments Off on Windows: Is the exe or DLL 32-bit or 64-bit?

    Java MQ Code to List All Local Queues Filtering by Current Depth

    If you have done the following runmqsc command to display all local queues filtering by current queue depth of a queue manager:

    DIS QL(*) WHERE (CURDEPTH GT 0)

    And you wanted to do the same thing via a program, here is a fully functioning Java MQ example that will connect to a remote queue manager, issue a PCF “Inquire Queue” command filtering the current queue depth (CURDEPTH), get the PCF response messages, loop through the PCF responses and output the information. You can download the source code from here.

    For more information about the PCF “Inquire Queue” command, go to MQ KnowLedge Center here.

    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Hashtable;
    
    import com.ibm.mq.MQException;
    import com.ibm.mq.MQQueueManager;
    import com.ibm.mq.constants.CMQC;
    import com.ibm.mq.constants.CMQCFC;
    import com.ibm.mq.headers.MQDataException;
    import com.ibm.mq.headers.pcf.PCFMessage;
    import com.ibm.mq.headers.pcf.PCFMessageAgent;
    
    /**
     * Program Name
     *  MQListQueueWithFilter01
     *
     * Description
     *  This java class issues a PCF "inquire queue" request message for all ("*") local queues 
     *  with a queue depth greater than 0 (zero) of a remote queue manager. 
     *  
     *  This PCF code is equivalent to issuing the runmqsc command of:
     *     DIS QL(*) WHERE (CURDEPTH GT 0) 
     *
     * Sample Command Line Parameters
     *  -m MQA1 -h 127.0.0.1 -p 1414 -c TEST.CHL -u UserID -x Password
     *
     * @author Roger Lacroix
     */
    public class MQListQueueWithFilter01
    {
       private static final SimpleDateFormat  LOGGER_TIMESTAMP = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
    
       private Hashtable<String,String> params;
       private Hashtable<String,Object> mqht;
       private String qMgrName;
    
       public MQListQueueWithFilter01()
       {
          super();
          params = new Hashtable<String,String>();
          mqht = new Hashtable<String,Object>();
       }
    
       /**
        * Make sure the required parameters are present.
        * @return true/false
        */
       private boolean allParamsPresent()
       {
          boolean b = params.containsKey("-h") && params.containsKey("-p") &&
                      params.containsKey("-c") && params.containsKey("-m") &&
                      params.containsKey("-u") && params.containsKey("-x");
          if (b)
          {
             try
             {
                Integer.parseInt((String) params.get("-p"));
             }
             catch (NumberFormatException e)
             {
                b = false;
             }
          }
    
          return b;
       }
    
       /**
        * Extract the command-line parameters and initialize the MQ HashTable.
        * @param args
        * @throws IllegalArgumentException
        */
       private void init(String[] args) throws IllegalArgumentException
       {
          int port = 1414;
          if (args.length > 0 && (args.length % 2) == 0)
          {
             for (int i = 0; i < args.length; i += 2)
             {
                params.put(args[i], args[i + 1]);
             }
          }
          else
          {
             throw new IllegalArgumentException();
          }
    
          if (allParamsPresent())
          {
             qMgrName = (String) params.get("-m");
             
             try
             {
                port = Integer.parseInt((String) params.get("-p"));
             }
             catch (NumberFormatException e)
             {
                port = 1414;
             }
             
             mqht.put(CMQC.CHANNEL_PROPERTY, params.get("-c"));
             mqht.put(CMQC.HOST_NAME_PROPERTY, params.get("-h"));
             mqht.put(CMQC.PORT_PROPERTY, new Integer(port));
             mqht.put(CMQC.USER_ID_PROPERTY, params.get("-u"));
             mqht.put(CMQC.PASSWORD_PROPERTY, params.get("-x"));
    
             // I don't want to see MQ exceptions at the console.
             MQException.log = null;
          }
          else
          {
             throw new IllegalArgumentException();
          }
       }
       
       /**
        * Handle connecting to the queue manager, issuing PCF command then 
        * looping through PCF response messages and disconnecting from 
        * the queue manager. 
        */
       private void doPCF()
       {
          MQQueueManager qMgr = null;
          PCFMessageAgent agent = null;
          PCFMessage   request = null;
          PCFMessage[] responses = null;
          
          try
          {
             qMgr = new MQQueueManager(qMgrName, mqht);
             MQListQueueWithFilter01.logger("successfully connected to "+ qMgrName);
    
             agent = new PCFMessageAgent(qMgr);
             MQListQueueWithFilter01.logger("successfully created agent");
          
             // https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.ref.adm.doc/q087800_.htm
             request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q);
             
             /**
              * You can explicitly set a queue name like "TEST.Q1" or
              * use a wild card like "TEST.*"
              */
             request.addParameter(CMQC.MQCA_Q_NAME, "*");
             
             // Add parameter to request only local queues
             request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_LOCAL);
             
             // Add parameter to request only queue name and current depth
             request.addParameter(CMQCFC.MQIACF_Q_ATTRS, new int [] { 
                                                                      CMQC.MQCA_Q_NAME,
                                                                      CMQC.MQIA_CURRENT_Q_DEPTH
                                                                    });
    
             // Add filter to only return responses with a queue depth greater than 0 (zero)  i.e. non-zero queue depth
             request.addFilterParameter(CMQC.MQIA_CURRENT_Q_DEPTH, CMQCFC.MQCFOP_GREATER, 0);
    
             responses = agent.send(request);
             
             MQListQueueWithFilter01.logger("responses.length="+responses.length);
             
             for (int i = 0; i < responses.length; i++)
             {
                if ( ((responses[i]).getCompCode() == CMQC.MQCC_OK) &&
                     ((responses[i]).getParameterValue(CMQC.MQCA_Q_NAME) != null) )
                {
                   String name = responses[i].getStringParameterValue(CMQC.MQCA_Q_NAME);
                   if (name != null)
                      name = name.trim();
    
                   int depth = responses[i].getIntParameterValue(CMQC.MQIA_CURRENT_Q_DEPTH);
                   
                   MQListQueueWithFilter01.logger("Name="+name + " : depth="+depth);
                }
             }
          }
          catch (MQException e)
          {
             MQListQueueWithFilter01.logger("CC=" +e.completionCode + " : RC=" + e.reasonCode);
          }
          catch (IOException e)
          {
             MQListQueueWithFilter01.logger("IOException:" +e.getLocalizedMessage());
          }
          catch (MQDataException e)
          {
             MQListQueueWithFilter01.logger("MQDataException:" +e.getLocalizedMessage());
          }
          finally
          {
             try
             {
                if (agent != null)
                {
                   agent.disconnect();
                   MQListQueueWithFilter01.logger("disconnected from agent");
                }
             }
             catch (MQDataException e)
             {
                MQListQueueWithFilter01.logger("CC=" +e.completionCode + " : RC=" + e.reasonCode);
             }
    
             try
             {
                if (qMgr != null)
                {
                   qMgr.disconnect();
                   MQListQueueWithFilter01.logger("disconnected from "+ qMgrName);
                }
             }
             catch (MQException e)
             {
                MQListQueueWithFilter01.logger("CC=" +e.completionCode + " : RC=" + e.reasonCode);
             }
          }
       }
    
       /**
        * A simple logger method
        * @param data
        */
       public static void logger(String data)
       {
          String className = Thread.currentThread().getStackTrace()[2].getClassName();
    
          // Remove the package info.
          if ( (className != null) && (className.lastIndexOf('.') != -1) )
             className = className.substring(className.lastIndexOf('.')+1);
    
          System.out.println(LOGGER_TIMESTAMP.format(new Date())+" "+className+": "+Thread.currentThread().getStackTrace()[2].getMethodName()+": "+data);
       }
    
       public static void main(String[] args)
       {
          MQListQueueWithFilter01 mqlqs = new MQListQueueWithFilter01();
          
          try
          {
             mqlqs.init(args);
             mqlqs.doPCF();
          }
          catch (IllegalArgumentException e)
          {
             MQListQueueWithFilter01.logger("Usage: java MQListQueueWithFilter01 -m QueueManagerName -h host -p port -c channel -u UserID -x Password");
             System.exit(1);
          }
    
          System.exit(0);
       }
    }

    Regards,
    Roger Lacroix
    Capitalware Inc.

    Capitalware, HPE NonStop, IBM i (OS/400), IBM MQ, Java, Linux, macOS (Mac OS X), Open Source, PCF, Programming, Unix, Windows Comments Off on Java MQ Code to List All Local Queues Filtering by Current Depth

    Excelsior Jet and macOS Mojave

    Well, here’s a little oddity that Excelsior Jet users should know about.

    On Friday, I decided to upgrade my MacBook Pro from High Sierra (v10.13) to Mojave (v10.14). Everything went smoothly. I checked the version level and it said I was running v10.14.2.

    Note: I have Excelsior Jet 15.3 Professional installed on my MacBook Pro.

    I played around with a bunch of stuff and tested my applications, life was good. When I started Excelsior Jet’s Control Panel (or Jet Pack II), the window showed it was loading (progress bar) but locked up when it went to display the main screen and would never complete (transparent/ghost window frame). I killed it and try again (and again) – always the same results, a locked application.

    On the bright-side, at least the non-GUI versions of Excelsior Jet’s Control Panel and Jet Pack II worked.

    After a whole lot of swearing, I sent Excelsior Jet’s support an email but of course it was late Friday afternoon, so you know what that means – talk to you on Monday. 🙁

    On Sunday, I was fooling around with my MacBook Pro and when I was in the App Store, it said there were updates, so I applied them. My MacBook Pro was upgraded to v10.14.5. After it was rebooted, both Jet Control Panel and Jet Pack worked perfectly. I was a happy camper! 🙂

    First off, what the hell is Apple doing? On Friday, why did it only upgrade my MacBook Pro to v10.14.2 if v10.14.5 was already available? Dumb, really dumb.

    Secondly, what screw-up did Apple do to the OS such that it would cause issues with an application which they clearly fixed in either v10.14.3 or v10.14.4 or v10.14.5.

    Early Monday morning, I received an email from Excelsior Jet support. The email contained a hotfix #4 for Excelsior Jet 15.3 and instructions for installing it. I said I was good to go and would wait for the maintenance release. In a follow up email, they said to apply either the hotfix or maintenance before distributing my application(s) because other users might be running macOS v10.14.2.

    So, this is an FYI to Excelsior Jet users. Be forewarned.

    Regards,
    Roger Lacroix
    Capitalware Inc.

    Java, macOS (Mac OS X), Programming Comments Off on Excelsior Jet and macOS Mojave

    Java MQ Code to List All Local Queues and their Attributes

    If you have done the following runmqsc command to display all local queues with attributes of a queue manager:

    DIS QL(*) ALL

    And you wanted to do the same thing via a program, here is a fully functioning Java MQ example that will connect to a remote queue manager, issue a PCF “Inquire Queue” command, get the PCF response messages, loop through the PCF responses and output the information. You can download the source code from here.

    For more information about the PCF “Inquire Queue” command, go to MQ KnowLedge Center here.

    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Hashtable;
    
    import com.ibm.mq.MQException;
    import com.ibm.mq.MQQueueManager;
    import com.ibm.mq.constants.CMQC;
    import com.ibm.mq.constants.CMQCFC;
    import com.ibm.mq.headers.MQDataException;
    import com.ibm.mq.headers.pcf.PCFMessage;
    import com.ibm.mq.headers.pcf.PCFMessageAgent;
    
    /**
     * Program Name
     *  MQListQueueAttributes01
     *
     * Description
     *  This java class issues a PCF "inquire queue" request message for all ("*") local queues 
     *  of a remote queue manager. 
     *  
     *  This PCF code is equivalent to issuing the runmqsc command of:
     *     DIS QL(*) ALL 
     *
     * Sample Command Line Parameters
     *  -m MQA1 -h 127.0.0.1 -p 1414 -c TEST.CHL -u UserID -x Password
     *
     * @author Roger Lacroix
     */
    public class MQListQueueAttributes01
    {
       private static final SimpleDateFormat  LOGGER_TIMESTAMP = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
    
       private Hashtable<String,String> params;
       private Hashtable<String,Object> mqht;
       private String qMgrName;
    
       public MQListQueueAttributes01()
       {
          super();
          params = new Hashtable<String,String>();
          mqht = new Hashtable<String,Object>();
       }
    
       /**
        * Make sure the required parameters are present.
        * @return true/false
        */
       private boolean allParamsPresent()
       {
          boolean b = params.containsKey("-h") && params.containsKey("-p") &&
                      params.containsKey("-c") && params.containsKey("-m") &&
                      params.containsKey("-u") && params.containsKey("-x");
          if (b)
          {
             try
             {
                Integer.parseInt((String) params.get("-p"));
             }
             catch (NumberFormatException e)
             {
                b = false;
             }
          }
    
          return b;
       }
    
       /**
        * Extract the command-line parameters and initialize the MQ HashTable.
        * @param args
        * @throws IllegalArgumentException
        */
       private void init(String[] args) throws IllegalArgumentException
       {
          int port = 1414;
          if (args.length > 0 && (args.length % 2) == 0)
          {
             for (int i = 0; i < args.length; i += 2)
             {
                params.put(args[i], args[i + 1]);
             }
          }
          else
          {
             throw new IllegalArgumentException();
          }
    
          if (allParamsPresent())
          {
             qMgrName = (String) params.get("-m");
             
             try
             {
                port = Integer.parseInt((String) params.get("-p"));
             }
             catch (NumberFormatException e)
             {
                port = 1414;
             }
             
             mqht.put(CMQC.CHANNEL_PROPERTY, params.get("-c"));
             mqht.put(CMQC.HOST_NAME_PROPERTY, params.get("-h"));
             mqht.put(CMQC.PORT_PROPERTY, new Integer(port));
             mqht.put(CMQC.USER_ID_PROPERTY, params.get("-u"));
             mqht.put(CMQC.PASSWORD_PROPERTY, params.get("-x"));
    
             // I don't want to see MQ exceptions at the console.
             MQException.log = null;
          }
          else
          {
             throw new IllegalArgumentException();
          }
       }
       
       /**
        * Handle connecting to the queue manager, issuing PCF command then 
        * looping through PCF response messages and disconnecting from 
        * the queue manager. 
        */
       private void doPCF()
       {
          MQQueueManager qMgr = null;
          PCFMessageAgent agent = null;
          PCFMessage   request = null;
          PCFMessage[] responses = null;
          
          try
          {
             qMgr = new MQQueueManager(qMgrName, mqht);
             MQListQueueAttributes01.logger("successfully connected to "+ qMgrName);
    
             agent = new PCFMessageAgent(qMgr);
             MQListQueueAttributes01.logger("successfully created agent");
          
             // https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.ref.adm.doc/q087800_.htm
             request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q);
             
             /**
              * You can explicitly set a queue name like "TEST.Q1" or
              * use a wild card like "TEST.*"
              */
             request.addParameter(CMQC.MQCA_Q_NAME, "*");
             
             // Add parameter to request only local queues
             request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_LOCAL);
    
             // Add parameter to request all of the attributes of the queue
             request.addParameter(CMQCFC.MQIACF_Q_ATTRS, new int [] { CMQCFC.MQIACF_ALL });
    
             responses = agent.send(request);
             
             MQListQueueAttributes01.logger("responses.length="+responses.length);
             
             for (int i = 0; i < responses.length; i++)
             {
                if ( ((responses[i]).getCompCode() == CMQC.MQCC_OK) &&
                     ((responses[i]).getParameterValue(CMQC.MQCA_Q_NAME) != null) )
                {
                   String name = responses[i].getStringParameterValue(CMQC.MQCA_Q_NAME);
                   if (name != null)
                      name = name.trim();
    
                   int depth = responses[i].getIntParameterValue(CMQC.MQIA_CURRENT_Q_DEPTH);
                   int maxDepth = responses[i].getIntParameterValue(CMQC.MQIA_MAX_Q_DEPTH);
                   
                   // many, many attributes can be listed - see web page mentioned above.
                   
                   MQListQueueAttributes01.logger("Name="+name + " : depth="+depth + " : max_depth="+maxDepth);
                }
             }
          }
          catch (MQException e)
          {
             MQListQueueAttributes01.logger("CC=" +e.completionCode + " : RC=" + e.reasonCode);
          }
          catch (IOException e)
          {
             MQListQueueAttributes01.logger("IOException:" +e.getLocalizedMessage());
          }
          catch (MQDataException e)
          {
             MQListQueueAttributes01.logger("MQDataException:" +e.getLocalizedMessage());
          }
          finally
          {
             try
             {
                if (agent != null)
                {
                   agent.disconnect();
                   MQListQueueAttributes01.logger("disconnected from agent");
                }
             }
             catch (MQDataException e)
             {
                MQListQueueAttributes01.logger("CC=" +e.completionCode + " : RC=" + e.reasonCode);
             }
    
             try
             {
                if (qMgr != null)
                {
                   qMgr.disconnect();
                   MQListQueueAttributes01.logger("disconnected from "+ qMgrName);
                }
             }
             catch (MQException e)
             {
                MQListQueueAttributes01.logger("CC=" +e.completionCode + " : RC=" + e.reasonCode);
             }
          }
       }
    
       /**
        * A simple logger method
        * @param data
        */
       public static void logger(String data)
       {
          String className = Thread.currentThread().getStackTrace()[2].getClassName();
    
          // Remove the package info.
          if ( (className != null) && (className.lastIndexOf('.') != -1) )
             className = className.substring(className.lastIndexOf('.')+1);
    
          System.out.println(LOGGER_TIMESTAMP.format(new Date())+" "+className+": "+Thread.currentThread().getStackTrace()[2].getMethodName()+": "+data);
       }
    
       public static void main(String[] args)
       {
          MQListQueueAttributes01 mqlqs = new MQListQueueAttributes01();
          
          try
          {
             mqlqs.init(args);
             mqlqs.doPCF();
          }
          catch (IllegalArgumentException e)
          {
             MQListQueueAttributes01.logger("Usage: java MQListQueueAttributes01 -m QueueManagerName -h host -p port -c channel -u UserID -x Password");
             System.exit(1);
          }
    
          System.exit(0);
       }
    }

    Regards,
    Roger Lacroix
    Capitalware Inc.

    Capitalware, HPE NonStop, IBM i (OS/400), IBM MQ, Java, Linux, macOS (Mac OS X), Open Source, PCF, Programming, Unix, Windows Comments Off on Java MQ Code to List All Local Queues and their Attributes

    Its All Fun & Games Until Your Message Is Empty!

    I’ve collected a bunch of JSON sample files from the internet for testing with MQ Visual Edit. It is always good to have a variety of samples when testing features of a program rather than making up a couple of your own.

    I was doing some testing with MQ Visual Edit and the JSON samples when I happen to select a particular JSON sample, clicked the “Format” button (pretty-print format) then clicked “Save to Queue” button. The message on the queue was empty (data length of zero). At first, I thought I did some wrong. I did the test again a couple of times, sure enough, every time the message was empty.

    I looked at MQ Visual Edit’s logfile and it had an IOException. The IOException getMessage method error text was:

    Input length = 1

    And the IOException getClause method error text was null.

    I shock my head because the first character of the JSON sample was a bracket. i.e. {
    And the second character was a new-line. The error message made no sense. I ran the code in the Eclipse debugger and everything looked fine. The code extracts the data from the window panel and as it does the MQMessage writeString method, it throws the IOException.

    Again more head shaking and thinking WTF!! Why doesn’t MQ like the JSON data. I went through all of the other samples, and not a problem. How can MQ not like a particular JSON sample. More specifically, I can load the same unformatted JSON sample without issue, it was only when I performed the pretty-print formatting that there was an issue.

    The formatted JSON sample is 146 lines long, so I started to rip it apart. After several iteration, I found an interesting difference.

    Here is a line from the unformatted JSON sample:

    "album_information":"<p>Funk-punk party starters !!! performs live at KEXP\u2019s \u201cBean Room\u201d stage during our broadcast at the Capitol Hill Block Party 2010.\u00a0\u00a0\u00a0 <br \/><\/p>",

    And here is the same line from the formatted JSON sample:

    "album_information": "<p>Funk-punk party starters !!! performs live at KEXP’s “Bean Room” stage during our broadcast at the Capitol Hill Block Party 2010.    <br /></p>",

    MQ Visual Edit uses the GSON library to perform the pretty-print formatting. If you look near the end of the unformatted line you will see 3 \u00a0 characters. For some strange reason, the GSON library converts \u00a0 to the real character of 0xA0 and MQMessage writeString method throws an exception when it encounters those characters. Weird, very weird.

    So I put together a little hack that fixes the issue:

    boolean flag = false;
    for (int i=0; i < panelData.length(); i++)
    {
       if (Character.UnicodeBlock.of(panelData.charAt(i)) != Character.UnicodeBlock.BASIC_LATIN)
       {
          flag = true;
          break;
       }
    }
    
    if (flag)  //found weird character
       mqMsg.write(panelData.getBytes());
    else
       mqMsg.writeString(panelData);

    If anyone has a better way of doing it then I’m all ears. 🙂

    Now, I did do some searches on turning off Unicode conversion in the GSON library but all I found was for disabling HTML conversion. This is what my GSON code looks like for pretty-print formatting:

    Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
    JsonParser jp = new JsonParser();
    JsonElement je = jp.parse(origMsgData);  // original message data
    
    panel.setText(gson.toJson(je));

    Again, if anyone has a better way of doing it then I’m all ears. 🙂

    Regards,
    Roger Lacroix
    Capitalware Inc.

    IBM MQ, Java, Linux, macOS (Mac OS X), MQ Visual Edit, Programming, Windows 2 Comments

    IBM MQ Fix Pack 8.0.0.12 Released

    IBM has just released Fix Pack 8.0.0.12 for IBM MQ
    https://www.ibm.com/support/docview.wss?uid=ibm10884142

    Regards,
    Roger Lacroix
    Capitalware Inc.

    Fix Packs for MQ, IBM i (OS/400), IBM MQ, Linux, Unix, Windows Comments Off on IBM MQ Fix Pack 8.0.0.12 Released

    Capitalware’s Help Desk is Disabled

    Well, I just discovered another casualty of upgrading to PHP 7.0, it broke the open source “PHP Support Tickets” (aka Help Desk) that I am using.

    Now when I try to use it, I get the error message:

    Fatal error: Uncaught Error: Call to undefined function mysql_connect() in /aaa/bbb/ccc/phpst/class/functions.php:56 Stack trace: #0 /aaa/bbb/ccc/phpst/index.php(35): include_once() #1 {main} thrown in /aaa/bbb/ccc/phpst/class/functions.php on line 56

    A quick internet search and I discovered that “mysql_connect” was depreciated in PHP 5.5.0 and removed in PHP 7.0. I don’t have time to attempt to fix/update all MySQL database calls in it. So, I have removed the link to the Help Desk from Capitalware’s web site.

    Regards,
    Roger Lacroix
    Capitalware Inc.

    Capitalware Comments Off on Capitalware’s Help Desk is Disabled