# Python MQI Class Wrappers. High level classes that for the MQI
# Extension. These present an object interface to MQI.
#
# Author: L. Smithson (lsmithson@open-networks.co.uk)
# Author: Dariusz Suchojad (dsuch at gefira.pl)
#
# DISCLAIMER
# You are free to use this code in any way you like, subject to the
# Python & IBM disclaimers & copyrights. I make no representations
# about the suitability of this software for any purpose. It is
# provided "AS-IS" without warranty of any kind, either express or
# implied. So there.
#

""" 
PyMQI - Python MQI Wrapper Classes

These classes wrap the pymqe low level MQI calls. They present an OO
interface with a passing resemblance MQI C++.

Classes are also provided for easy use of the MQI structure parameters
(MQMD, MQGMO etc.) from Python. These allow Python scripts to set/get
structure members by attribute, dictionary etc.

The classes are:

    * MQOpts - Base class for defining MQI parameter structures.
    * gmo - MQI MQGMO structure class.
    * pmo - MQI MQPMO structure class.
    * od - MQI MQOD structure class.
    * md - MQI MQMD structure class.
    * cd - MQI MQCD structure class
    * sco - MQI MQSCO structure class
    
    * QueueManager - Queue Manager operations.    
    * Queue - Queue operations
    * PCFExecute - Programmable Command Format operations
    
    * Error - Base class for pymqi errors.
    * MQMIError - MQI specific error
    * PYIFError - Pymqi error
    
The following MQI operations are supported:

    * MQCONN, MQDISC (QueueManager.connect()/QueueManager.disconnect())
    * MQCONNX (QueueManager.connectWithOptions())
    * MQOPEN/MQCLOSE (Queue.open(), Queue.close())
    * MQPUT/MQPUT1/MQGET (Queue.put(), QueueManager.put1(), Queue.get())
    * MQCMIT/MQBACK (QueueManager.commit()/QueueManager.backout())
    * MQBEGIN (QueueuManager.begin())
    * MQINQ (QueueManager.inquire(), Queue.inquire())
    * MQSET (Queue.set())
    * And various MQAI PCF commands.
    
The supported command levels (from 5.0 onwards) for the version of MQI
linked with this module are available in the tuple pymqi.__mqlevels__.
For a client build, pymqi.__mqbuild__ is set to the string 'client',
otherwise it is set to 'server'.

To use this package, connect to the Queue Manager (using
QueueManager.connect()), then open a queue (using Queue.open()). You
may then put or get messages on the queue (using Queue.put(),
Queue.get()), as required.

Where possible, pymqi assumes the MQI defaults for all parameters.

Like MQI C++, pymqi can defer a queue connect until the put/get call.

Pymqi maps all MQI warning & error status to the MQMIError
exception. Errors detected by pymqi itself raise the PYIFError
exception. Both these exceptions are subclasses of the Error class.

MQI constants are defined in the CMQC module. PCF constants are
defined in CMQCFC.

PCF commands and inquiries are executed by calling a MQCMD_* method on
an instance of a PCFExecute object.

Pymqi is thread safe. Pymqi objects have the same thread scope as
their MQI counterparts.

"""

# Stdlib
import struct, exceptions, types, threading

# PyMQI
import pymqe, CMQC, CMQCFC, CMQXC

__version__ = "1.0.1"
__mqlevels__ = pymqe.__mqlevels__
__mqbuild__ = pymqe.__mqbuild__


#
# 64bit suppport courtesy of Brent S. Elmer, Ph.D. (mailto:webe3vt@aim.com)
# 
# On 64 bit machines when MQ is compiled 64bit, MQLONG is an int defined
# in /opt/mqm/inc/cmqc.h or wherever your MQ installs to.
#
# On 32 bit machines, MQLONG is a long and many other MQ data types are set to MQLONG
#
# So, set MQLONG_TYPE to 'i' for 64bit MQ and 'l' for 32bit MQ so that the
# conversion from the Python data types to C data types in the MQ structures
# will work correctly.
#

# Are we running 64 bit?
if struct.calcsize("P") == 8:
   MQLONG_TYPE = 'i' # 64 bit
else:
   MQLONG_TYPE = 'l' # 32 bit

#######################################################################
#

# MQI Python<->C Structure mapping. MQI uses lots of parameter passing
# structures in its API. These classes are used to set/get the
# parameters in the style of Python dictionaries & keywords. Pack &
# unpack calls are used to convert between python class attributes and
# 'C' structures, suitable for passing in/out of C functions.
#
# The MQOpts class defines common operations for structure definition,
# default values setting, member set/get and translation to & from 'C'
# structures. Specializations construct MQOpts with a list specifying
# structure member names, their default values, and pack/unpack
# formats. MQOpts uses this list to setup class attributes
# corresponding to the structure names, set up attribute defaults, and
# builds a format string usable by the struct package to translate to
# 'C' structures.
#
#######################################################################


class MQOpts:
    """Base class for packing/unpacking MQI Option structures. It is
    constructed with a list defining the member/attribute name,
    default value (from the CMQC module) and the member pack format
    (see the struct module for the formats). The list format is:

      [['Member0', CMQC.DEFAULT_VAL0, 'fmt1']
       ['Member1', CMQC.DEFAULT_VAL1, 'fmt2']
         ...
      ]

    MQOpts defines common methods to allow structure members to be
    set/get as attributes (foo.Member0 = 42), set/get as dictionary
    items (foo['Member0'] = 42) or set as keywords (foo.set(Member0 =
    42, Member1 = 'flipperhat'). The ctor can be passed an optional
    keyword list to initialize the structure members to non-default
    values. The get methods returns all attributes as a dictionary.

    The pack() method packs all members into a 'C' structure according
    to the format specifiers passed to the ctor. The packing order is
    as specified in the list passed to the ctor. Pack returns a string
    buffer, which can be passed directly to the MQI 'C' calls.

    The unpack() method does the opposite of pack. It unpacks a string
    buffer into an MQOpts instance.

    Applications are not expected to use MQOpts directly. Instead,
    MQOpts is sub-classed as particular MQI structures."""

    def __init__(self, list, **kw):
        """MQOpts(memberList [,**kw])

        Initialise the option structure. 'list' is a list of structure
    member names, default values and pack/unpack formats. 'kw' is an
    optional keyword dictionary that may be used to override default
    values set by MQOpts sub-classes."""
        
        self.__list = list[:]
        self.__format = ''
        # Creat the structure members as instance attributes and build
        # the struct.pack/unpack format string. The attribute name is
        # identical to the 'C' structure member name.
        for i in list:
            setattr(self, i[0], i[1])
            self.__format = self.__format + i[2]
        apply(MQOpts.set, (self,), kw)
        
    def pack(self):
        """ pack()
        
        Pack the attributes into a 'C' structure to be passed to MQI
        calls. The pack order is as defined to the MQOpts
        ctor. Returns the structure as a string buffer"""
        
        # Build tuple for struct.pack() argument. Start with format
        # string. 
        args = [self.__format]
        # Now add the current attribute values to the tuple
        for i in self.__list:
            v = getattr(self, i[0])
            # Flatten attribs that are arrays
            if type(v) is types.ListType:
                for x in v:
                    args.append(x)
            else:
                args.append(v)
        return apply(struct.pack, args)

    def unpack(self, buff):
        """unpack(buff)

        Unpack a 'C' structure 'buff' into self."""
        
        # Unpack returns a tuple of the unpacked data, in the same
        # order (I hope!) as in the ctor's list arg.
        r = struct.unpack(self.__format, buff)
        x = 0
        for i in self.__list:
            setattr(self, i[0], r[x])
            x = x + 1

    def set(self, **kw):
        """set(**kw)

        Set a structure member using the keyword dictionary 'kw'. An
        AttributeError exception is raised for invalid member
        names."""
        
        for i in kw.keys():
            # Only set if the attribute already exists. getattr raises
            # an exception if it doesn't.
            getattr(self, str(i))
            setattr(self, str(i), kw[i])
            
    def __setitem__(self, key, value):
        """__setitem__(key, value)

        Set the structure member attribute 'key' to 'value', as in
        obj['Flop'] = 42.
        """

        # Only set if the attribute already exists. getattr raises an
        # exception if it doesn't.
        getattr(self, key)
        setattr(self, key, value)

    def get(self):
        """get()

        Return a dictionary of the current structure member
        values. The dictionary is keyed by a 'C' member name."""

        d = {}
        for i in self.__list:
            d[i[0]] = getattr(self, i[0])
        return d

    def __getitem__(self, key):
        """__getitem__(key)

        Return the member value associated with key, as in print
        obj['Flop']."""
        return getattr(self, key)

    def __str__(self):
        """__str__()

        Pretty Print Structure."""
        
        rv = ''
        for i in self.__list:
            rv = rv + str(i[0]) + ': ' + str(getattr(self, i[0])) + '\n'
        # Chop the trailing newline
        return rv[:-1]
    
    def __repr__(self):
        """__repr__()

        Return the packed buffer as a printable string."""
        return str(self.pack())

#
# Sub-classes of MQOpts representing real MQI structures.
#
        
class gmo(MQOpts):
    """gmo(**kw)

    Construct a MQGMO Structure with default values as per MQI. The
    default values may be overridden by the optional keyword arguments
    'kw'."""

    def __init__(self, **kw):
        opts = [['StrucId', CMQC.MQGMO_STRUC_ID, '4s'],
            ['Version', CMQC.MQGMO_VERSION_1, MQLONG_TYPE],
            ['Options', CMQC.MQGMO_NO_WAIT, MQLONG_TYPE],
            ['WaitInterval', 0, MQLONG_TYPE],
            ['Signal1', 0, MQLONG_TYPE],
            ['Signal2', 0, MQLONG_TYPE],
            ['ResolvedQName', '', '48s'],
            ['MatchOptions', CMQC.MQMO_MATCH_MSG_ID+CMQC.MQMO_MATCH_CORREL_ID, MQLONG_TYPE],
            ['GroupStatus', CMQC.MQGS_NOT_IN_GROUP, 'b'], 
            ['SegmentStatus', CMQC.MQSS_NOT_A_SEGMENT, 'b'],
            ['Segmentation', CMQC.MQSEG_INHIBITED, 'b'],
            ['Reserved1', ' ', 'c'],
            ['MsgToken', '', '16s'],
            ['ReturnedLength', CMQC.MQRL_UNDEFINED, MQLONG_TYPE],]
            
        if "7.0" in pymqe.__mqlevels__:
            opts += [
                ['Reserved2', 0L, MQLONG_TYPE],
                ['MsgHandle', 0L, 'q']]
                
        apply(MQOpts.__init__, (self, tuple(opts)), kw)


class pmo(MQOpts):
    """pmo(**kw)

    Construct a MQPMO Structure with default values as per MQI. The
    default values may be overridden by the optional keyword arguments
    'kw'."""

    def __init__(self, **kw):
        opts = [
            ['StrucId', CMQC.MQPMO_STRUC_ID, '4s'],
            ['Version', CMQC.MQPMO_VERSION_1, MQLONG_TYPE],
            ['Options', CMQC.MQPMO_NONE, MQLONG_TYPE],
            ['Timeout', -1, MQLONG_TYPE],
            ['Context', 0, MQLONG_TYPE],
            ['KnownDestCount', 0, MQLONG_TYPE],
            ['UnknownDestCount', 0, MQLONG_TYPE],
            ['InvalidDestCount', 0, MQLONG_TYPE],
            ['ResolvedQName', '', '48s'],
            ['ResolvedQMgrName', '', '48s'],
            ['RecsPresent', 0, MQLONG_TYPE],
            ['PutMsgRecFields',  0, MQLONG_TYPE],
            ['PutMsgRecOffset', 0, MQLONG_TYPE],
            ['ResponseRecOffset', 0, MQLONG_TYPE],
            ['PutMsgRecPtr', 0, 'P'],
            ['ResponseRecPtr', 0, 'P']]
            
        if "7.0" in pymqe.__mqlevels__:
            opts += [
                ['OriginalMsgHandle', 0L, 'q'],
                ['NewMsgHandle', 0L, 'q'],
                ['Action', 0L, MQLONG_TYPE],
                ['PubLevel', 0L, MQLONG_TYPE]]
            
        apply(MQOpts.__init__, (self, tuple(opts)), kw)

class od(MQOpts):
    """od(**kw)

    Construct a MQOD Structure with default values as per MQI. The
    default values may be overridden by the optional keyword arguments
    'kw'."""

    def __init__(self, **kw):
        opts = [['StrucId', CMQC.MQOD_STRUC_ID, '4s'],
            ['Version', CMQC.MQOD_VERSION_1, MQLONG_TYPE],
            ['ObjectType', CMQC.MQOT_Q, MQLONG_TYPE],
            ['ObjectName', '', '48s'],
            ['ObjectQMgrName', '', '48s'],
            ['DynamicQName', 'AMQ.*', '48s'],
            ['AlternateUserId', '', '12s'],
            ['RecsPresent', 0, MQLONG_TYPE],
            ['KnownDestCount', 0, MQLONG_TYPE],
            ['UnknownDestCount', 0, MQLONG_TYPE],
            ['InvalidDestCount', 0, MQLONG_TYPE],
            ['ObjectRecOffset', 0, MQLONG_TYPE],
            ['ResponseRecOffset', 0, MQLONG_TYPE],
            ['ObjectRecPtr', 0, 'P'],
            ['ResponseRecPtr', 0, 'P'],
            ['AlternateSecurityId', '', '40s'],
            ['ResolvedQName', '', '48s'],
            ['ResolvedQMgrName', '', '48s'],]
            
        if "7.0" in pymqe.__mqlevels__:
            opts += [
            
                # ObjectString
                ['ObjectStringVSPtr', 0, 'P'],
                ['ObjectStringVSOffset', 0L, MQLONG_TYPE],
                ['ObjectStringVSBufSize', 0L, MQLONG_TYPE],
                ['ObjectStringVSLength', 0L, MQLONG_TYPE],
                ['ObjectStringVSCCSID', 0L, MQLONG_TYPE],
                
                # SelectionString
                ['SelectionStringVSPtr', 0, 'P'],
                ['SelectionStringVSOffset', 0L, MQLONG_TYPE],
                ['SelectionStringVSBufSize', 0L, MQLONG_TYPE],
                ['SelectionStringVSLength', 0L, MQLONG_TYPE],
                ['SelectionStringVSCCSID', 0L, MQLONG_TYPE],
                
                # ResObjectString
                ['ResObjectStringVSPtr', 0, 'P'],
                ['ResObjectStringVSOffset', 0L, MQLONG_TYPE],
                ['ResObjectStringVSBufSize', 0L, MQLONG_TYPE],
                ['ResObjectStringVSLength', 0L, MQLONG_TYPE],
                ['ResObjectStringVSCCSID', 0L, MQLONG_TYPE],
                
                ['ResolvedType', -3L, MQLONG_TYPE]]
                
            # For 64bit platforms MQLONG is an int and this pad
            # needs to be here for WMQ 7.0
            if MQLONG_TYPE == 'i':
                opts += [['pad','', '4s']]
            
        apply(MQOpts.__init__, (self, tuple(opts)), kw)
    

class md(MQOpts):
    """md(**kw)

    Construct a MQMD Structure with default values as per MQI. The
    default values may be overridden by the optional keyword arguments
    'kw'."""

    def __init__(self, **kw):
        apply(MQOpts.__init__, (self, (
            ['StrucId', CMQC.MQMD_STRUC_ID, '4s'],
            ['Version', CMQC.MQMD_VERSION_1, MQLONG_TYPE],
            ['Report', CMQC.MQRO_NONE, MQLONG_TYPE],
            ['MsgType', CMQC.MQMT_DATAGRAM, MQLONG_TYPE],
            ['Expiry', CMQC.MQEI_UNLIMITED, MQLONG_TYPE],
            ['Feedback', CMQC.MQFB_NONE, MQLONG_TYPE],
            ['Encoding', CMQC.MQENC_NATIVE, MQLONG_TYPE],
            ['CodedCharSetId', CMQC.MQCCSI_Q_MGR, MQLONG_TYPE],
            ['Format', '', '8s'],
            ['Priority', CMQC.MQPRI_PRIORITY_AS_Q_DEF, MQLONG_TYPE],
            ['Persistence', CMQC.MQPER_PERSISTENCE_AS_Q_DEF, MQLONG_TYPE],
            ['MsgId', '', '24s'],
            ['CorrelId', '', '24s'],
            ['BackoutCount', 0, MQLONG_TYPE],
            ['ReplyToQ', '', '48s'],
            ['ReplyToQMgr', '', '48s'],
            ['UserIdentifier', '', '12s'],
            ['AccountingToken', '', '32s'],
            ['ApplIdentityData', '', '32s'],
            ['PutApplType', CMQC.MQAT_NO_CONTEXT, MQLONG_TYPE],
            ['PutApplName', '', '28s'],
            ['PutDate', '', '8s'],
            ['PutTime', '', '8s'],
            ['ApplOriginData', '', '4s'],
            ['GroupId', '', '24s'],
            ['MsgSeqNumber', 1, MQLONG_TYPE],
            ['Offset', 0, MQLONG_TYPE],
            ['MsgFlags', CMQC.MQMF_NONE, MQLONG_TYPE],
            ['OriginalLength', CMQC.MQOL_UNDEFINED, MQLONG_TYPE])), kw)
            

# MQCONNX code courtesy of John OSullivan (mailto:jos@onebox.com)
# SSL additions courtesy of Brian Vicente (mailto:sailbv@netscape.net)

class cd(MQOpts):
    """cd(**kw)

    Construct a MQCD Structure with default values as per MQI. The
    default values may be overridden by the optional keyword arguments
    'kw'."""
    
    # The MQCD_VERSION & MQCD_LENGTH_* we're going to use depend on the WMQ
    # version we had been compiled with but it is not known on Python side
    # until runtime so set it once here when first importing pymqi
    # (originally written by Brent S. Elmer, Ph.D. (mailto:webe3vt@aim.com)).
    
    if '7.0' in pymqe.__mqlevels__:
        _mqcd_version = CMQXC.MQCD_VERSION_9
        _mqcd_current_length = CMQXC.MQCD_LENGTH_9
            
    elif '6.0' in pymqe.__mqlevels__:
        _mqcd_version = CMQXC.MQCD_VERSION_8
        _mqcd_current_length = CMQXC.MQCD_LENGTH_8
        
    elif '5.3' in pymqe.__mqlevels__:
        _mqcd_version = CMQXC.MQCD_VERSION_7
        _mqcd_current_length = CMQXC.MQCD_LENGTH_7
        
    else:
        # The default version in MQCD_DEFAULT in cmqxc.h is MQCD_VERSION_6
        _mqcd_version = CMQXC.MQCD_VERSION_6
        _mqcd_current_length = CMQXC.MQCD_LENGTH_6
    
    def __init__(self, **kw):
        opts = []
        opts += [
            ['ChannelName', '', '20s'],
            ['Version', self._mqcd_version, MQLONG_TYPE],
            ['ChannelType', CMQC.MQCHT_SENDER, MQLONG_TYPE],
            ['TransportType', CMQC.MQXPT_LU62, MQLONG_TYPE],
            ['Desc', '', '64s'],
            ['QMgrName', '', '48s'],
            ['XmitQName', '', '48s'],
            ['ShortConnectionName', '', '20s'],
            ['MCAName', '', '20s'],
            ['ModeName', '', '8s'],
            ['TpName', '', '64s'],
            ['BatchSize', 50L, MQLONG_TYPE],
            ['DiscInterval', 6000L, MQLONG_TYPE],
            ['ShortRetryCount', 10L, MQLONG_TYPE],
            ['ShortRetryInterval', 60L, MQLONG_TYPE],
            ['LongRetryCount', 999999999L, MQLONG_TYPE],
            ['LongRetryInterval', 1200L, MQLONG_TYPE],
            ['SecurityExit', '', '128s'],
            ['MsgExit', '', '128s'],
            ['SendExit', '', '128s'],
            ['ReceiveExit', '', '128s'],
            ['SeqNumberWrap', 999999999L, MQLONG_TYPE],
            ['MaxMsgLength', 4194304L, MQLONG_TYPE],
            ['PutAuthority', CMQC.MQPA_DEFAULT, MQLONG_TYPE],
            ['DataConversion', CMQC.MQCDC_NO_SENDER_CONVERSION, MQLONG_TYPE],
            ['SecurityUserData', '', '32s'],
            ['MsgUserData', '', '32s'],
            ['SendUserData', '', '32s'],
            ['ReceiveUserData', '', '32s'],
            ['UserIdentifier', '', '12s'],
            ['Password', '', '12s'],
            ['MCAUserIdentifier', '', '12s'],
            ['MCAType', CMQC.MQMCAT_PROCESS, MQLONG_TYPE],
            ['ConnectionName', '', '264s'],
            ['RemoteUserIdentifier', '', '12s'],
            ['RemotePassword', '', '12s'],
            ['MsgRetryExit', '', '128s'],
            ['MsgRetryUserData', '', '32s'],
            ['MsgRetryCount', 10L, MQLONG_TYPE],
            ['MsgRetryInterval', 1000L, MQLONG_TYPE],
            ['HeartbeatInterval', 300L, MQLONG_TYPE],
            ['BatchInterval', 0L, MQLONG_TYPE],
            ['NonPersistentMsgSpeed', CMQC.MQNPMS_FAST, MQLONG_TYPE],
            ['StrucLength', self._mqcd_current_length, MQLONG_TYPE],
            ['ExitNameLength', CMQC.MQ_EXIT_NAME_LENGTH, MQLONG_TYPE],
            ['ExitDataLength', CMQC.MQ_EXIT_DATA_LENGTH, MQLONG_TYPE],
            ['MsgExitsDefined', 0L, MQLONG_TYPE],
            ['SendExitsDefined', 0L, MQLONG_TYPE],
            ['ReceiveExitsDefined', 0L, MQLONG_TYPE],
            ['MsgExitPtr', 0, 'P'],
            ['MsgUserDataPtr', 0, 'P'],
            ['SendExitPtr', 0, 'P'],
            ['SendUserDataPtr', 0, 'P'],
            ['ReceiveExitPtr', 0, 'P'],
            ['ReceiveUserDataPtr', 0, 'P'],
            ['ClusterPtr', 0, 'P'],
            ['ClustersDefined', 0L, MQLONG_TYPE],
            ['NetworkPriority', 0L, MQLONG_TYPE],
            ['LongMCAUserIdLength', 0L, MQLONG_TYPE],
            ['LongRemoteUserIdLength', 0L, MQLONG_TYPE],
            ['LongMCAUserIdPtr', 0, 'P'],
            ['LongRemoteUserIdPtr', 0, 'P'],
            ['MCASecurityId', '', '40s'],
            ['RemoteSecurityId', '', '40s']]

        # If SSL is supported, append the options. SSL support is
        # implied by 5.3.
        if "5.3" in pymqe.__mqlevels__:
            opts += [['SSLCipherSpec','','32s'],
                     ['SSLPeerNamePtr',0,'P'],
                     ['SSLPeerNameLength',0L,MQLONG_TYPE],
                     ['SSLClientAuth',0L,MQLONG_TYPE],
                     ['KeepAliveInterval',-1,MQLONG_TYPE],
                     ['LocalAddress','','48s'],
                     ['BatchHeartbeat',0L,MQLONG_TYPE]]
        else:
            # No mqaiExecute means no 5.3, so redefine the struct version
            opts[1] = ['Version', CMQC.MQCD_VERSION_6, MQLONG_TYPE]

        if "6.0" in pymqe.__mqlevels__:
            opts += [['HdrCompList', [0L, -1L], '2' + MQLONG_TYPE],
                     ['MsgCompList', [0] + 15 * [-1L], '16' + MQLONG_TYPE],
                     ['CLWLChannelRank', 0L, MQLONG_TYPE],
                     ['CLWLChannelPriority', 0L, MQLONG_TYPE],
                     ['CLWLChannelWeight', 50L, MQLONG_TYPE],
                     ['ChannelMonitoring', 0L, MQLONG_TYPE],
                     ['ChannelStatistics', 0L, MQLONG_TYPE]]
                     
        if "7.0" in pymqe.__mqlevels__:
            opts += [['SharingConversations', 10, MQLONG_TYPE],
                     ['PropertyControl', 0, MQLONG_TYPE],      # 0 = MQPROP_COMPATIBILITY 
                     ['MaxInstances', 999999999, MQLONG_TYPE],
                     ['MaxInstancesPerClient', 999999999, MQLONG_TYPE],
                     ['ClientChannelWeight', 0, MQLONG_TYPE],
                     ['ConnectionAffinity', 1, MQLONG_TYPE]]  # 1 = MQCAFTY_PREFERRED
                     
        # In theory, the pad should've been placed right before the 'MsgExitPtr'
        # attribute, however setting it there makes no effect and that's why
        # it's being set here, as a last element in the list.
        if MQLONG_TYPE == 'i':
            opts += [['pad','', '4s']]

        apply(MQOpts.__init__, (self, tuple(opts)), kw)

        

# SCO Class for SSL Support courtesy of Brian Vicente (mailto:sailbv@netscape.net)
class sco(MQOpts):
    """sco(**kw)
    
    Construct a MQSCO Structure with default values as per MQI. The
    default values maybe overridden by the optional keyword arguments
    'kw'. """
    
    def __init__(self, **kw):
         opts = [
        ['StrucId', CMQC.MQSCO_STRUC_ID, '4s'],            
            ['Version', CMQC.MQSCO_VERSION_1, MQLONG_TYPE],
        ['KeyRepository', '', '256s'],
        ['CryptoHardware', '', '256s'],
        ['AuthInfoRecCount', 0L, MQLONG_TYPE],
        ['AuthInfoRecOffset', 0L, MQLONG_TYPE],
        ['AuthInfoRecPtr', 0, 'P']]

         # Add new SSL fields defined in 6.0 and update version to 2
         if "6.0" in pymqe.__mqlevels__:
            opts += [['KeyResetCount', 0L, MQLONG_TYPE],
                     ['FipsRequired', 0L, MQLONG_TYPE]]

         apply(MQOpts.__init__, (self, tuple(opts)), kw)

#
# A utility to convert a MQ constant to its string mnemonic by groping
# a module dictonary
#

class _MQConst2String:

    def __init__(self, module, prefix):
        self.__module = module;
        self.__prefix = prefix
        self.__stringDict = {}
        self.__lock = threading.Lock()

    def __build(self):
        # Lazily build the dictionary of consts vs. their
        # mnemonic strings from the given module dict. Only those 
        # attribute that begins with the prefix are considered.
        self.__lock.acquire()
        if len(self.__stringDict) == 0:
            pfxlen = len(self.__prefix)
            for i in self.__module.__dict__.keys():
                if i[0:pfxlen] == self.__prefix:
                    newKey, newVal = self.__module.__dict__[i], i
                    self.__stringDict[newKey] = newVal
        self.__lock.release()
        
    def __getitem__(self, code):
        self.__build()
        return self.__stringDict[code]

    def has_key(self, key):
        self.__build()
        return self.__stringDict.has_key(key)

    
#######################################################################
#
# Exception class that encapsulates MQI completion/reason codes.
#
#######################################################################

class Error(exceptions.Exception):
    """Base class for all pymqi exceptions."""
    pass

class MQMIError(Error):
    """Exception class for MQI low level errors."""
    errStringDicts = (_MQConst2String(CMQC, "MQRC_"), _MQConst2String(CMQCFC, "MQRCCF_"),)

    def __init__(self, comp, reason):
        """MQMIError(comp, reason)

        Construct the error object with MQI completion code 'comp' and
        reason code 'reason'."""
        
        self.comp, self.reason = comp, reason

    def __str__(self):
        """__str__()

        Pretty print the exception object."""
        
        return 'MQI Error. Comp: %d, Reason %d: %s' % (self.comp, self.reason, self.errorAsString())

    def errorAsString(self):
        """errorAsString()

        Return the exception object MQI warning/failed reason as its
        mnemonic string."""

        if self.comp == CMQC.MQCC_OK:
            return 'OK'
        elif self.comp == CMQC.MQCC_WARNING:
            pfx = 'WARNING: '
        else:
            pfx = 'FAILED: '

        for d in MQMIError.errStringDicts:
            if d.has_key(self.reason):
                return pfx + d[self.reason]
        return pfx + 'WTF? Error code ' + str(self.reason) + ' not defined'
        
class PYIFError(Error):
    """Exception class for errors generated by pymqi."""
    def __init__(self, e):
        """PYIFError(e)

        Construct the error object with error string 'e'."""
        self.error = e

    def __str__(self):
        """__str__

        Pretty print the exception object."""
        return 'PYMQI Error: ' + str(self.error)

    
#######################################################################
#
# MQI Verbs
#
#######################################################################

class QueueManager:
    """QueueManager encapsulates the connection to the Queue Manager. By
    default, the Queue Manager is implicitly connected. If required,
    the connection may be deferred until a call to connect().
    """
    
    def __init__(self, name = ''):
        """QueueManager(name = '')

        Connect to the Queue Manager 'name' (default value ''). If
        'name' is None, don't connect now, but defer the connection
        until connect() is called."""
        
        self.__handle = None
        self.__name = name
        self.__qmobj = None
        if name != None:
            self.connect(name)

    def __del__(self):
        """__del__()

        Disconnect the Queue Manager, if connected."""

        if self.__handle:
            if self.__qmobj:
                try:
                    pymqe.MQCLOSE(self.__handle, self.__qmobj)
                except:
                    pass
            try:
                self.disconnect()
            except:
                pass
                
    def connect(self, name):
        """connect(name)

        Connect immediately to the Queue Manager 'name'."""

        rv = pymqe.MQCONN(name)
        if rv[1]:
            raise MQMIError(rv[1], rv[2])
        self.__handle = rv[0]
        self.__name = name
        

# MQCONNX code courtesy of John OSullivan (mailto:jos@onebox.com)
# SSL additions courtesy of Brian Vicente (mailto:sailbv@netscape.net)
# Connect options suggested by Jaco Smuts (mailto:JSmuts@clover.co.za)

    def connectWithOptions(self, name, *bwopts, **kw):
        """connectWithOptions(name [, opts=cnoopts][ ,cd=mqcd][ ,sco=mqsco])
           connectWithOptions(name, cd, [sco])
           
        Connect immediately to the Queue Manager 'name', using the
        optional MQCNO Options opts, the optional MQCD connection
        descriptor cd and the optional MQSCO SSL options sco.

        The second form is defined for backward compatibility with
        older (broken) versions of pymqi. It connects immediately to
        the Queue Manager 'name', using the MQCD connection descriptor
        cd and the optional MQSCO SSL options sco."""

        # Deal with old style args
        bwoptsLen = len(bwopts)
        if bwoptsLen:
            if bwoptsLen > 2:
                raise exceptions.TypeError('Invalid options: %s' % bwopts) 
            if bwoptsLen >= 1:
                kw['cd'] = bwopts[0]
            if bwoptsLen == 2:
                kw['sco'] = bwopts[1]
            
        else:
            # New style args
            for k in kw.keys():
                if k not in ('opts', 'cd', 'sco'):
                    raise exceptions.TypeError('Invalid option: %s' % k) 
            
        options = CMQC.MQCNO_NONE
        ocd = cd()
        if kw.has_key('opts'):
            options = kw['opts']
        if kw.has_key('cd'):
            ocd = kw['cd']
        if kw.has_key('sco'):
            rv = pymqe.MQCONNX(name, options, ocd.pack(), kw['sco'].pack())
        else:
            rv = pymqe.MQCONNX(name, options, ocd.pack())
        if rv[1]:
            raise MQMIError(rv[1], rv[2])
        self.__handle = rv[0]
        self.__name = name
        
    def connectTCPClient(self, name, cd, channelName, connectString):
        """connectTCPClient(name, cd, channelName, connectString)

        Connect immediately to the remote Queue Manager 'name', using
        a TCP Client connection, with channnel 'channelName' and the
        TCP connection string 'connectString'. All other connection
        optons come from 'cd'."""

        cd.ChannelName = channelName
        cd.ConnectionName = connectString
        cd.ChannelType = CMQC.MQCHT_CLNTCONN
        cd.TransportType = CMQC.MQXPT_TCP
        self.connectWithOptions(name, cd)
        
    def disconnect(self):
        """disconnect()

        Disconnect from queue manager, if connected."""
        
        if self.__handle:
            rv = pymqe.MQDISC(self.__handle)
        else:
            raise PYIFError('not connected')
        
    def getHandle(self):
        """getHandle()

        Get the queue manager handle. The handle is used for other
        pymqi calls."""
        
        if self.__handle:
            return self.__handle
        else:
            raise PYIFError('not connected')

    def begin(self):
        """begin()

        Begin a new global transaction. 
        """

        rv = pymqe.MQBEGIN(self.__handle)
        if rv[0]:
            raise MQMIError(rv[0], rv[1])

    def commit(self):
        """commit()
        
        Commits any outstanding gets/puts in the current unit of work."""
        
        rv = pymqe.MQCMIT(self.__handle)
        if rv[0]:
            raise MQMIError(rv[0], rv[1])
        
    def backout(self):
        """backout()

        Backout any outstanding gets/puts in the current unit of
        work."""
        
        rv = pymqe.MQBACK(self.__handle)
        if rv[0]:
            raise MQMIError(rv[0], rv[1])
        
    def put1(self, qDesc, msg, *opts):
        """put1(qDesc, msg [, mDesc, putOpts])

        Put the single message in string buffer 'msg' on the queue
        using the MQI PUT1 call. This encapsulates calls to MQOPEN,
        MQPUT and MQCLOSE. put1 is the optimal way to put a single
        message on a queue.

        qDesc identifies the Queue either by name (if its a string),
        or by MQOD (if its a pymqi.od() instance).

        mDesc is the pymqi.md() MQMD Message Descriptor for the
        message. If it is not passed, or is None, then a default md()
        object is used.

        putOpts is the pymqi.pmo() MQPMO Put Message Options structure
        for the put1 call. If it is not passed, or is None, then a
        default pmo() object is used.

        If mDesc and/or putOpts arguments were supplied, they may be
        updated by the put1 operation."""

        mDesc, putOpts = apply(commonQArgs, opts)
        if putOpts == None:
            putOpts = pmo()

        # Now send the message
        rv = pymqe.MQPUT1(self.__handle, makeQDesc(qDesc).pack(),
                          mDesc.pack(), putOpts.pack(), msg)
        if rv[-2]:
            raise MQMIError(rv[-2], rv[-1])
        mDesc.unpack(rv[0])
        putOpts.unpack(rv[1])
   

    def inquire(self, attribute):
        """inquire(attribute)

        Inquire on queue manager 'attribute'. Returns either the
        integer or string value for the attribute."""

        if self.__qmobj == None:
            # Make an od for the queue manager, open the qmgr & cache result
            qmod = od(ObjectType = CMQC.MQOT_Q_MGR, ObjectQMgrName = self.__name)
            rv = pymqe.MQOPEN(self.__handle, qmod.pack(), CMQC.MQOO_INQUIRE)
            if rv[-2]:
                raise MQMIError(rv[-2], rv[-1])
            self.__qmobj = rv[0]
        rv = pymqe.MQINQ(self.__handle, self.__qmobj, attribute)
        if rv[1]:
            raise MQMIError(rv[-2], rv[-1])
        return rv[0]


# Some support functions for Queue ops.
def makeQDesc(qDescOrString):
    "Maybe make MQOD from string. Module Private"
    if type(qDescOrString) is types.StringType:
        return od(ObjectName = qDescOrString)
    else:
        return qDescOrString


def commonQArgs(*opts):
    "Process args common to put/get/put1. Module Private."
    l = len(opts)
    if l > 2:
        raise exceptions.TypeError, 'Too many args'
    mDesc = None
    pgOpts = None
    if l > 0:
        mDesc = opts[0]
    if l == 2:
        pgOpts = opts[1]
    if mDesc == None:
        mDesc = md()
    return(mDesc, pgOpts)
    

class Queue:

    """Queue encapsulates all the Queue I/O operations, including
    open/close and get/put. A QueueManager object must be already
    connected. The Queue may be opened implicitly on construction, or
    the open may be deferred until a call to open(), put() or
    get(). The Queue to open is identified either by a queue name
    string (in which case a default MQOD structure is created using
    that name), or by passing a ready constructed MQOD class."""


    def __realOpen(self):
        "Really open the queue."
        if self.__qDesc == None:
            raise PYIFError, 'The Queue Descriptor has not been set.'
        rv = pymqe.MQOPEN(self.__qMgr.getHandle(),
                          self.__qDesc.pack(), self.__openOpts)
        if rv[-2]:
            raise MQMIError(rv[-2], rv[-1])
        self.__qHandle = rv[0]
        self.__qDesc.unpack(rv[1])
    
    def __init__(self, qMgr, *opts):
        """Queue(qMgr, [qDesc [,openOpts]])

        Associate a Queue instance with the QueueManager object 'qMgr'
        and optionally open the Queue.

        If qDesc is passed, it identifies the Queue either by name (if
        its a string), or by MQOD (if its a pymqi.od() instance). If
        qDesc is not defined, then the Queue is not opened
        immediately, but deferred to a subsequent call to open().

        If openOpts is passed, it specifies queue open options, and
        the queue is opened immediately. If openOpts is not passed,
        the queue open is deferred to a subsequent call to open(),
        put() or get().

        The following table clarifies when the Queue is opened:

           qDesc  openOpts   When opened
             N       N       open()
             Y       N       open() or get() or put()
             Y       Y       Immediately
        """
        
        self.__qMgr = qMgr
        self.__qHandle = self.__qDesc = self.__openOpts = None
        l = len(opts)
        if l > 2:
            raise exceptions.TypeError, 'Too many args'
        if l > 0:
            self.__qDesc = makeQDesc(opts[0])
        if l == 2:
            self.__openOpts = opts[1]
            self.__realOpen()

    def __del__(self):
        """__del__()

        Close the Queue, if it has been opened."""
        
        if self.__qHandle:
            try:
                self.close()
            except:
                pass

    def open(self, qDesc, *opts):
        """open(qDesc [,openOpts])

        Open the Queue specified by qDesc. qDesc identifies the Queue
        either by name (if its a string), or by MQOD (if its a
        pymqi.od() instance). If openOpts is passed, it defines the
        Queue open options, and the Queue is opened immediately. If
        openOpts is not passed, the Queue open is deferred until a
        subsequent put() or get() call."""

        l = len(opts)
        if l > 1:
            raise exceptions.TypeError, 'Too many args'
        if self.__qHandle:
            raise PYIFError('The Queue is already open')
        self.__qDesc = makeQDesc(qDesc)
        if l == 1:
            self.__openOpts = opts[0]
            self.__realOpen()

    
    def put(self, msg, *opts):
        """put(msg[, mDesc ,putOpts])

        Put the string buffer 'msg' on the queue. If the queue is not
        already open, it is opened now with the option 'MQOO_OUTPUT'.

        mDesc is the pymqi.md() MQMD Message Descriptor for the
        message. If it is not passed, or is None, then a default md()
        object is used.

        putOpts is the pymqi.pmo() MQPMO Put Message Options structure
        for the put call. If it is not passed, or is None, then a
        default pmo() object is used.

        If mDesc and/or putOpts arguments were supplied, they may be
        updated by the put operation."""

        mDesc, putOpts = apply(commonQArgs, opts)
        if putOpts == None:
            putOpts = pmo()
        # If queue open was deferred, open it for put now
        if not self.__qHandle:
            self.__openOpts = CMQC.MQOO_OUTPUT
            self.__realOpen()
        # Now send the message
        rv = pymqe.MQPUT(self.__qMgr.getHandle(), self.__qHandle, mDesc.pack(),
                         putOpts.pack(), msg)
        if rv[-2]:
            raise MQMIError(rv[-2], rv[-1])
        mDesc.unpack(rv[0])
        putOpts.unpack(rv[1])
        
    def get(self, maxLength = None, *opts):
        """get([maxLength [, mDesc, getOpts]])

        Return a message from the queue. If the queue is not already
        open, it is opened now with the option 'MQOO_INPUT_AS_Q_DEF'.

        maxLength, if present, specifies the maximum length for the
        message. If the message received exceeds maxLength, then the
        behavior is as defined by MQI and the getOpts argument.

	If maxLength is not specified, or is None, then the entire
	message is returned regardless of its size. This may require
	multiple calls to the underlying MQGET API.

        mDesc is the pymqi.md() MQMD Message Descriptor for receiving
        the message. If it is not passed, or is None, then a default
        md() object is used.
        
        getOpts is the pymqi.gmo() MQGMO Get Message Options
        structure for the get call. If it is not passed, or is None,
        then a default gmo() object is used.

        If mDesc and/or getOpts arguments were supplied, they may be
        updated by the get operation."""
        
        mDesc, getOpts = apply(commonQArgs, opts)
        if getOpts == None:
            getOpts = gmo()
        # If queue open was deferred, open it for put now
        if not self.__qHandle:
            self.__openOpts = CMQC.MQOO_INPUT_AS_Q_DEF
            self.__realOpen()

        # Truncated message fix thanks to Maas-Maarten Zeeman
        if maxLength == None:
	    length = 4096
        else:
	    length = maxLength
            
        rv = pymqe.MQGET(self.__qMgr.getHandle(), self.__qHandle,
                        mDesc.pack(), getOpts.pack(), length)

        if not rv[-2]:
            # Everything A OK
            mDesc.unpack(rv[1])
            getOpts.unpack(rv[2])
            return rv[0]

        # Some error. If caller supplied buffer, maybe it wasn't big
        # enough, so just return the error/warning.
        # CAVEAT: If message truncated, this exception loses the
        # partially filled buffer.
        if maxLength != None and maxLength >= 0:
            raise MQMIError(rv[-2], rv[-1])

        # Default buffer used, if not truncated, give up
        if rv[-1] != CMQC.MQRC_TRUNCATED_MSG_FAILED:
            raise MQMIError(rv[-2], rv[-1])

        # Message truncated, but we know its size. Do another MQGET
        # to retrieve it from the queue. 
        mDesc.unpack(rv[1])  # save the message id
	length = rv[-3]
        rv = pymqe.MQGET(self.__qMgr.getHandle(), self.__qHandle,
                         mDesc.pack(), getOpts.pack(), length)
        if rv[-2]:
            raise MQMIError(rv[-2], rv[-1])
        mDesc.unpack(rv[1])
        getOpts.unpack(rv[2])
        return rv[0]

    def close(self, options = CMQC.MQCO_NONE):
        """close([options])
        
        Close a queue, using options."""

        if not self.__qHandle:
            raise PYIFError('not open')
        rv = pymqe.MQCLOSE(self.__qMgr.getHandle(), self.__qHandle, options)
        if rv[0]:
            raise MQMIError(rv[-2], rv[-1])
        self.__qHandle = self.__qDesc = self.__openOpts = None
        
    def inquire(self, attribute):
        """inquire(attribute)

        Inquire on queue 'attribute'. If the queue is not already
        open, it is opened for Inquire. Returns either the integer or
        string value for the attribute."""

        if not self.__qHandle:
            self.__openOpts = CMQC.MQOO_INQUIRE
            self.__realOpen()
        rv = pymqe.MQINQ(self.__qMgr.getHandle(), self.__qHandle, attribute)
        if rv[1]:
            raise MQMIError(rv[-2], rv[-1])
        return rv[0]
    
    def set(self, attribute, arg):
        """set(attribute, arg)

        Sets the Queue attribute to arg."""
        if not self.__qHandle:
            self.__openOpts = CMQC.MQOO_SET
            self.__realOpen()
        rv = pymqe.MQSET(self.__qMgr.getHandle(), self.__qHandle, attribute, arg)
        if rv[1]:
            raise MQMIError(rv[-2], rv[-1])


#
# This piece of magic shamelessly plagiarised from xmlrpclib.py. It
# works a bit like a C++ STL functor.
#
class _Method:
    def __init__(self, pcf, name):
        self.__pcf = pcf
        self.__name = name

    def __getattr__(self, name):
        return _Method(self.__pcf, "%s.%s" % (self.__name, name))
        
    def __call__(self, *args):
        if self.__name[0:7] == 'CMQCFC.':
            self.__name = self.__name[7:]
        if self.__pcf.qm:
            qmHandle = self.__pcf.qm.getHandle()
        else:
            qmHandle = self.__pcf.getHandle()
        if len(args):
            rv = pymqe.mqaiExecute(qmHandle, CMQCFC.__dict__[self.__name], args[0])
        else:
            rv = pymqe.mqaiExecute(qmHandle, CMQCFC.__dict__[self.__name])
        if rv[1]:
            raise MQMIError(rv[-2], rv[-1])
        return rv[0]

#
# Execute a PCF commmand. Inspired by Maas-Maarten Zeeman
#

class PCFExecute(QueueManager):

    """Send PCF commands or inquiries using the MQAI
    interface. PCFExecute must be connected to the Queue Manager
    (using one of the techniques inherited from QueueManager) before
    its used. PCF commands are executed by calling a CMQCFC defined
    MQCMD_* method on the object.  """

    iaStringDict = _MQConst2String(CMQC, "MQIA_")
    caStringDict = _MQConst2String(CMQC, "MQCA_")

    def __init__(self, name = ''):
        """PCFExecute(name = '')

        Connect to the Queue Manager 'name' (default value '') ready
        for a PCF command. If name is a QueueManager instance, it is
        used for the connection, otherwise a new connection is made """

        if isinstance(name, QueueManager):
            self.qm = name
            QueueManager.__init__(self, None)
        else:
            self.qm = None
            QueueManager.__init__(self, name)
        
    def __getattr__(self, name):
        """MQCMD_*(attrDict)

        Execute the PCF command or inquiry, passing an an optional
        dictionary of MQ attributes.  The command must begin with
        MQCMD_, and must be one of those defined in the CMQCFC
        module. If attrDict is passed, its keys must be an attribute
        as defined in the CMQC or CMQCFC modules (MQCA_*, MQIA_*,
        MQCACH_* or MQIACH_*). The key value must be an int or string,
        as appropriate.

        If an inquiry is executed, a list of dictionaries (one per
        matching query) is returned. Each dictionary encodes the
        attributes and values of the object queried. The keys are as
        defined in the CMQC module (MQIA_*, MQCA_*), The values are
        strings or ints, as appropriate.

        If a command was executed, or no inquiry results are
        available, an empty listis returned.  """

        return _Method(self, name)

    def stringifyKeys(self, rawDict):
        """stringifyKeys(rawDict)

        Return a copy of rawDict with its keys converted to string
        mnemonics, as defined in CMQC. """
        
        rv = {}
        for k in rawDict.keys():
            if type(rawDict[k]) is types.StringType:
                d = PCFExecute.caStringDict
            else:
                d = PCFExecute.iaStringDict
            try:
                rv[d[k]] = rawDict[k]
            except KeyError, e:
                rv[k] = rawDict[k]
        return rv

        
#######################################################################
#
# TESTS
# This test code puts & gets a message on a queue. The queue manager
# is the default (''). With no arguments, the queue 'QTEST1' is
# used. This example is elaborated to show the pymqi features. It also
# messes about with inquire & set calls.
#######################################################################


if __name__ == '__main__':
    print 'pymqi version:', __version__, 'pymqe version:', pymqe.__version__, pymqe.__mqbuild__
    print 'MQI Version:', max(map(float, __mqlevels__))
    import sys, string
    if len(sys.argv) > 1:
        qName = sys.argv[1]
    else:
        qName = 'QTEST1'
    print 'Using qname', qName
    if pymqe.__mqbuild__ == 'server':
        # Defaults to queue manager named ''
        qMgr = QueueManager()
    else:
        qMgr = QueueManager(None)
        qMgr.connectTCPClient('les.queue.manager', cd(), "SYSTEM.DEF.SVRCONN", 'localhost(1414)')


    # Queue ctor can take a string as a queue name. Open is deferred
    # until the put() call.
    putQ = Queue(qMgr, qName)

    # Queue ctor can also take a fully formed object descriptor. This
    # queue is opened immediately.
    getQDesc = od(ObjectName=qName)
    getQ = Queue(qMgr, getQDesc)

    # Put a message. Because of the deferred open, the put queue is
    # opened now. Default values for message descriptor & put message
    # options are used.
    putQ.put('Hello pymqi!')

    # Now query the queue & the queue manager
    print 'Queue Manager name:', string.strip(qMgr.inquire(CMQC.MQCA_Q_MGR_NAME)),
    print 'last altered:', qMgr.inquire(CMQC.MQCA_ALTERATION_DATE),
    print 'on:', qMgr.inquire(CMQC.MQCA_ALTERATION_TIME)
    qmgrPlatform = qMgr.inquire(CMQC.MQIA_PLATFORM)
    platformDict = {3 : 'Unix', 5 : 'Windows', 11 : 'Windows/NT'}
    try:
        print 'Queue Manager Platform:', platformDict[qmgrPlatform]
    except:
        print 'Queue Manager Platform type:', qmgrPlatform
        
    inqQ = Queue(qMgr, qName)
    print 'Queue Depth:', inqQ.inquire(CMQC.MQIA_CURRENT_Q_DEPTH)
    print 'last altered:', inqQ.inquire(CMQC.MQCA_ALTERATION_DATE),
    print 'on:', inqQ.inquire(CMQC.MQCA_ALTERATION_TIME)

    # Now do a get
    try:
        # Default message descriptor
        msgDesc = md()

        # MQI structure members can be set using keywords...
        getOpts = gmo(Options = CMQC.MQGMO_WAIT)
        # ... or attributes
        getOpts.WaitInterval = CMQC.MQWI_UNLIMITED
        # Get message from queue.
        msg = getQ.get(None, msgDesc, getOpts)

        # Some structure parameters are updated by the MQI call.
        # Structure members can also be set/get as dictionary items.
        print "Read message '%s' from resolved queue %s" % (msg, getOpts['ResolvedQName'])
        print 'Queue depth now:', inqQ.inquire(CMQC.MQIA_CURRENT_Q_DEPTH)

        # Test set
        setQ = Queue(qMgr, qName)
        setQ.set(CMQC.MQIA_INHIBIT_GET, CMQC.MQQA_GET_INHIBITED)
        print 'INHIBIT_GET Value:', inqQ.inquire(CMQC.MQIA_INHIBIT_GET)
        setQ.set(CMQC.MQIA_INHIBIT_GET, CMQC.MQQA_GET_ALLOWED)
        setQ.set(CMQC.MQCA_TRIGGER_DATA, '%-64s' % 'Hello from Python!')
    # Catch & print any pymqi exception
    except Error, e:
        print 'Got error', e

    # Test PCF, if its available
    if "5.3" in pymqe.__mqlevels__:
        print 'PCF tests'
        # FIXME: Allow connection to be passed in
        pcf = PCFExecute(qMgr)
        print 'Pinged queue manager'
        pcf.MQCMD_PING_Q_MGR()
        print 'Querying Queue'
        results = pcf.stringifyKeys(pcf.CMQCFC.MQCMD_INQUIRE_Q({CMQC.MQCA_Q_NAME : qName})[0])
        print results
        for x in results:
            if type(x) is not types.StringType:
                print 'PCF key %d (value %s) is not known' % (x, results[x])
    else:
        print 'PCF/MQAI not available'
        
    print 'Test completed'



