By Marcus Daley
The explosive popularity of the Internet has brought a wealth of new ideas and paradigms to the computing world, many of which have taken root and flourished in our everyday lives. Along with these new ideas has come validation of old disciplines, and the strengthening of core technologies. Distributed computing via message queues is one of these older ideas. In the past, many high-end transactional systems used message queuing to send information among distributed applications. Although this is still true today, a significant portion of this space has been moved to distributed object technologies, such as DCOM and CORBA.
Moving to distributed objects has garnered a lot of press and focus from software engineers, making it appear that message queuing systems are slowly being phased out. This simply is not true. New uses for established messaging technologies are appearing almost as fast as they are with distributed objects. In fact, with the introduction of COM+, the marriage of the two technologies is just around the corner.
IBM, Sybase, Tibco, Microsoft, and others have built messaging middleware servers with several powerful capabilities in order to take advantage of the market for modern message-queuing systems. Middleware servers can share data between dissimilar systems; perform high-speed transaction processing, delayed messaging, offline communication, and asynchronous communication; and they provide power and transactional safety required by large enterprises. These things are offered in a package that works with heterogeneous networks and remote machines. And this is just a small list of the advantages and uses of middleware servers.
Enter MSMQ
Microsoft's answer to the messaging middleware market is Microsoft Message Queue Server (MSMQ). MSMQ is similar to products offered by others, but it has an advantage because it's tightly integrated with Windows NT through the Option Pack add-on. The only additional requirement needed for MSMQ is a working installation of Microsoft SQL Server on the host machine on which it's installed.
MSMQ is extremely powerful, and has capabilities often seen only in expensive middleware servers. Using a Connector Server and third-party add-ons can further extend MSMQ. For example, Level 8 Systems has developed a suite of cross-platform and cross-message-queue interoperability products to help extend MSMQ to non-Windows platforms. Their product is known as FalconMQ (MSMQ's original code name was Falcon), and brings MSMQ power to a range of systems that include UNIX, AS/400, VMS, and others. Extensions of this sort make MSMQ a valid choice as an enterprise messaging solution.
Although MSMQ is a relatively new product and seems simple in functionality, this is not the case. Complicated situations and potential failures exist in a product of this magnitude. Microsoft spent more than seven years building MSMQ and they've had several large development teams working on the product through all of its stages. This should be noted if you're considering building your own store-and-forward system.
Installing MSMQ
Installation of MSMQ takes place through the Windows NT Option Pack setup interface (see Figure 1).
Figure 1: The Microsoft Windows NT 4.0 Option Pack installation wizard.
One component of the Option Pack setup is "Microsoft Message Queue," and its checkbox should be selected when initiating the Option Pack setup process (see Figure 2).
Figure 2: Selecting Microsoft Message Queue for installation.
There are seven types of installations for MSMQ that can be used via the Option Pack installation wizard (see Figure 3).
Figure 3: The seven types of MSMQ installation (described in Figure 4).
They're explained briefly in Figure 4.
Primary Enterprise Controller (PEC) |
One PEC must exist on the network before any other installation can be completed. All configuration data, and the master copy of the information store, will be consigned to this machine. You can only have one PEC per enterprise network. All other MSMQ installations will only work if they join a PEC already on the network. PECs double as a PSC. Microsoft SQL Server is required for this installation. |
Primary Site Controller (PSC) |
A PSC must exist for every MSMQ site installation. Sites are a collection of MSMQ installations that represent a location. Communication between computers in a site will be fast and efficient because of the short distance between each client. Microsoft SQL Server is required for this installation. |
Backup Site Controller (BSC) |
A BSC stores backup information for a PEC or PSC. When a PEC or PSC fails, the BSC temporarily fills in for the failed server. Microsoft SQL Server is required for this installation. |
Routing Server |
Routing Servers route MSMQ information through the network. This installation doesn't require Microsoft SQL Server, and should only be used on a network with high MSMQ traffic. |
Independent Client |
Independent Clients maintain their own queues. Independent Clients send and receive messages while they aren't on the network. They're useful for laptops, and other servers that might be disconnected from the network for any period of time. Microsoft SQL Server isn't required for this installation. |
Dependent Client |
Dependent Clients are the thinnest sort of MSMQ client available. The server (PEC, PSC, BSC, etc.) supplies all MSMQ services. Up to 15 Dependent Clients can be supported by a server. This installation requires a network connection to a server. Microsoft SQL Server isn't required for this installation. |
RAS Connectivity Service |
The RAS Connectivity Service can be best compared to a proxy or forwarding service. This service enables RAS clients (most likely an Independent Client) to send and receive messages from computers other than the RAS server. |
Figure 4: The seven types of MSMQ installations using the Option Pack installation wizard.
There is another MSMQ installation, not available in the standard Windows NT 4.0 Option Pack installation, for Connector Servers. This type of installation is described in Figure 5, and is only available for Windows NT Server, Enterprise Edition.
Connector Server |
A Connector Server is a bridge between non-Microsoft queuing systems. Level 8 systems, with its FalconMQ product, is an example of a Connector Server. Connector Servers act as translators between other middleware providers, and are an added value for enterprises requiring this level of functionality. Connector Servers are beyond the scope of this article. |
Figure 5: This MSMQ installation is available only with Windows NT Server, Enterprise Edition.
The multiple acronyms and strange wording for each installation can be confusing. It's easiest to think of the different installation types as branches of a tree. The Primary Enterprise Controller (PEC) is the trunk of the tree and required for any other installations to work. Once a PEC has been installed, additional installations (branches) can take place. An installation can be as simple as a single Independent Client, or as complex as a Primary Site Controller (PSC) with a collection of dependent clients. Throughout this article we will assume that a PEC has been successfully installed. All example code and experiments you perform can be accomplished with a single PEC installation. However, it's more interesting to experiment with an installation combination such as an Independent Client and a remote PSC. Testing with multiple machines can be useful for many reasons.
When installing a PEC you're required to enter information concerning an Enterprise Name and a Site Name. Enterprise Names represent an entire enterprise of computers, and there can be only one per enterprise network. However, there are often multiple sites that represent a physical location of a collection of machines; this makes PSC installations valuable. For example, if a national company has offices in New York and Los Angeles, they'll need at least one PSC in each location. This ensures that MSMQ routing and transactions between computers within a site will be handled locally, maintaining network speed and efficiency.
MSMQ makes use of the MSMQ Information Store (MQIS), or Active Directory. MQIS is a distributed database used by every PEC, PSC, and Backup Site Controller (BSC). The database is distributed between these servers and is replicated on each machine to maintain consistency. The type of data stored in MQIS is configuration data and system information. MQIS causes the need for Microsoft SQL Server to be installed because MSMQ relies on it to maintain and provide quick access to this data. MQIS should go away with the Active Directory Service that comes with Windows 2000.
Queue Types
There are two types of queues that exist in MSMQ: application and system. An application queue is any queue created by a user. The three types of application queues are message, response, and administration (see Figure 6).
Message |
Message queues are created most often by applications. Messages are sent to, and read from, message queues. This type of queue is a simple, no-nonsense queue. |
Response |
Response queues behave the same way as message queues. To use a response queue, each message must specify the location of the response queue in its ResponseQueueInfo parameter. The response queue designates where return messages should be placed after the initial message has been processed from the message queue. |
Administration |
Administration queues are specialized and hold ACK or NACK acknowledgement messages. ACK or NACK messages can be generated when a message arrives at its destination. These messages are only generated when specified in a message's AdminQueueInfo parameter. |
Figure 6: The three types of application queues.
Application queues can be designated as public or private. Public queues can be seen by anyone in an enterprise. This means that any machine on the network can look up and perform authorized actions against a public queue because they're registered in the Active Directory (MQIS), and their properties are replicated across the enterprise. Most queues are created as public queues.
Private queues aren't registered with the Active Directory, making them faster and more efficient because they remain local to the machine that created them. This means they aren't normally viewable by other machines on the network. Private queues should only be used for special-purpose functions where local storage and efficiency are a priority.
The second type of queue that exists in MSMQ is a system queue. Whenever a machine is added to the enterprise, or an application queue is created, a system queue will be created. The system creates these queues to support users of the MSMQ system by logging current and past activity. The three types of system queues are journal, report, and dead-letter queues (see Figure 7).
Journal |
Journal queues are automatically created by MSMQ when a new machine is put on the enterprise, or when an application queue is created. Messages are only placed in the journal queue by request. This is done using a message's Journal property. |
Report |
Report queues are used for tracking messages. This is useful when messages are forwarded to multiple queues before being processed. Messages must request this service using the Trace property. |
Dead Letter |
Dead letter queues are the "trash cans" of MSMQ. Any messages that can't be delivered can be found in the appropriate dead letter queue. There are two types of dead letter queues. One is for failed transactional messages, and the other is for failed non-transactional messages. This behavior is automatic for all transactional messages, and must be requested for by non-transactional messages. |
Figure 7: The three types of system queues.
Messaging middleware servers wouldn't be complete if they didn't support transactions and security. MSMQ strongly supports both of these features.
MSMQ Transactional Support
When application queues are built, they can be flagged as either transactional or non-transactional. There are two major differences between non-transactional and transactional MSMQ queues. The first difference is that non-transactional queues can set a priority on each message that enters its queue, and transactional queues can't. This is useful when certain messages need to be processed before another. The second difference is that transactional queues require a transaction to send or receive messages, and non-transactional queues do not.
Transactional queues are most often used because they're safer and more reliable than queues without transactional guarantees. This is important in enterprise environments where each message is critical to the success of the company.
MSMQ supports three types of transactions: internal, external, and XA. Internal transactions are the easiest and most efficient transactions available to MSMQ users. In fact, they're only supported within MSMQ. This means that other servers, such as Microsoft SQL Server and Microsoft Transaction Server (MTS), can't participate in an MSMQ internal transaction. Because internal transactions have been built to work only with MSMQ applications, they've been optimized for the MSMQ environment. This is useful when an application is focused on simple transactions rather than performing lengthy transactions across multiple processes and environments. Internal transactions are demonstrated in the sample code later in this article.
External transactions are used in conjunction with the Distributed Transaction Coordinator (DTC). The DTC coordinates a transaction among MTS, Microsoft SQL Server, and MSMQ, so all three can share the same transaction when it spans multiple process spaces. For example, it's possible to build a simple Web page using a combination of Active Server Pages, MTS, Microsoft SQL Server, and MSMQ. If a customer submits a request to purchase a product, then a new transaction is started and it spans the entire customer request. This is done by calling the appropriate MTS package, making updates to Microsoft SQL Server, and then placing a message on an MSMQ queue that requests the product to be delivered to the customer. If any of these actions fail, the transaction should abort, roll back all changes, and return an error to the customer. You can imagine the customer dissatisfaction if partial transactions were allowed to complete. External transactions are powerful and useful in a situation like this.
XA transactions, like external transactions, span multiple process spaces. XA transactions are used when a transaction extends to a third-party system. The DTC invokes and responds to XA calls when MSMQ, Microsoft SQL Server, and MTS need to participate in an XA transaction. The X/Open group defines the XA interface; these transactions are common in non-Microsoft products. There is support for XA transactions and Connector Servers in order to integrate MSMQ products.
Secure Messaging
MSMQ has security features that include encryption, message authentication, and integrity. MSMQ supports one-way authentication, but doesn't support two-way or mutual authentication. One-way authentication means that the receiving application needs to determine the sender of a message. Access controls can be put on queues to protect against unauthorized access, and MSMQ ensures the integrity of each sent message. In other words, MSMQ guarantees the message can't be modified during transmission.
Internal and external electronic certificates can be used to certify a sender or receiver. Under ordinary circumstances, internal certificates are sufficient and useful when a receiver wants to authenticate the sender. External certificates carry information, such as a certificate-issuing authority and its signature, which adds to the validity of a sender.
The security features of MSMQ aren't explicitly used in the sample code in this article. However, most security features of MSMQ come free through using the defaults. This makes worrying about MSMQ security less of a chore for the average MSMQ application developer.
Programming MSMQ
There are two APIs from which developers can choose when developing for MSMQ. The first API is a C language interface. As a Java developer, this API is almost impossible to use without developing C DLLs, or ATL COM objects, that act as proxies. This leaves the second API, which is accessed through a collection of Automation-compatible COM servers. These COM objects provide an aggregation of robust interfaces that are easily used from Java. The COM components have numerous methods, properties, and events attached to them. As a Java developer you have access to these components and the services they provide.
Although the two APIs are almost identical, there are some differences that should be of interest. The C interfaces can do a few things that COM objects can't. One difference is the ability to access the connector message properties. COM components don't have access to the connector message properties that enable use of a Connector Server installation. This won't be a limitation unless you want to use a Connector Server, and then access those properties from Java. If this is the case, you'll need to create C/C++ proxy objects. Additionally, there are several event mechanisms missing from the COM components that are included in the C interface. There is only one event mechanism in the COM objects that requires you to make use of the com.ms.com.ConnectionPointCookie class (we'll cover this later in the article). Despite these shortcomings, most developers are able to easily derive the functionality they need from the available COM objects.
There are 10 COM servers accessible through Automation. Explanations for each of them appear in Figure 8. MSMQMessage, MSMQQueue, and MSMQQueueInfo are the most used of the 10 available COM objects. Other COM objects, such as the MSMQTransaction object and the MSMQEvent object, are only used in certain situations. Most of the Automation objects in Figure 8 are used in the sample code shown later in this article.
MSMQMessage |
Represents the properties and content of a message. This component performs the actual sending of a message to a specified queue. |
MSMQQueue |
Represents a single queue in a MSMQ enterprise. This component performs the receive, peek, and event registration functions. |
MSMQQueueInfo |
Represents the properties of a queue. This component creates a queue according to its own properties. It's used to open, update, or refresh the values of a queue. |
MSMQEvent |
Represents an event handler for one or many queues. This component acts as a funnel for events fired from the MSMQ system. |
MSMQQuery |
An object that enables an application to look up queues on an enterprise that meet a certain set of criteria. |
MSMQQueueInfos |
Represents a collection of queues that have been found by a MSMQQuery object. The collection can be iterated from top to bottom. |
MSMQTransaction |
Represents a single transaction. The transaction must be obtained through the MSMQTransactionDispenser, or the MSMQCoordinateTransactionDispenser. This includes internal, external, and XA transactions. |
MSMQTransactionDispenser |
An object that distributes instances of MSMQ internal transactions. |
MSMQCoordinateTransactionDispenser |
An object that distributes instances of MSMQ external transactions. These transactions are obtained from the DTC, so they can work with other transactional processes on the network. |
MSMQApplication |
An object that provides a method for retrieving the machine ID of a computer. |
Figure 8: COM objects accessible through Automation.
The JActiveX Tool
There is an important and necessary step when using the MSMQ COM objects from Java (or any COM object). This step is used when creating simple Java class wrappers to call a COM object. The reason for Java COM wrappers isn't explained in this article, and requires an understanding of COM and its relation to Java. However, Microsoft has provided a command-line-based program called JActiveX (Java to ActiveX) that automates almost the entire process. The JActiveX utility generates Java source code from type libraries. These type libraries are loaded into the JActiveX process where it discovers the interfaces, methods, etc. When the JActiveX-generated source code is compiled, the new classes are used to access the COM objects the type library represents. This simple wrapping of COM objects makes COM programming easy for Java developers, especially when using the MSMQ COM objects.
Explanations for many of the available JActiveX command-line arguments can be found in the Java 3.x SDK, or on Microsoft's Web site at http://www.microsoft.com/java/sdk/31/source/jsdk_pg_tools_jactivex_options.htm. The DLL containing the type library for the MSMQ system can be located in the \WINNT\System32 directory, and is named MQOA.DLL. JActiveX /javatlb c:\WINNT\System32\mqoa.dll demonstrates how to use the JActiveX tool to generate wrappers for the MSMQ COM objects.
The source code generated by the JActiveX tool will be placed in the \WINNT\Java\trustlib\mqoa directory on your machine. The source code is a combination of Java interfaces and classes that, once compiled, will work together to access the MSMQ COM objects shown in Figure 8. The source code needs to be compiled with the standard jvc compiler from a command-prompt window. The JActiveX-generated code will be used throughout the rest of this article.
Building a Simple Java Server
Writing a server that takes advantage of the MSMQ Automation objects is simple when using Java. For the purpose of this article, the sample server is a small piece of code that sends a message to MSMQ for an unspecified client to pick up and process. The client and server code samples can be labeled as a server, or a client, depending on the purpose of the code. The sample server code in Listing One (beginning on page XX) is designed to simulate a server that is sending updated messages to a client somewhere on the network via a queue. (The sample server and client code is available for download; see end of article for details.)
The source code for the server uses five of the 10 available Automation objects shown in Figure 8. The code demonstrates how to create a new queue, and how to send a message to that queue via an MSMQ internal transaction. Both of these actions are common, and will be used in most MSMQ applications.
Step-by-Step Discussion of the Server Source Code
Step One. When programmatically building a queue, first create an IMSMQQueueInfo object. This object has several important properties that comprise the state and features of the queue being created. The most important feature is the pathname property. This property designates where the queue will be placed on the MSMQ enterprise. This includes the name of the computer on which the queue will reside, as well as the name of the queue. The sample code in Listing One sets this property to ".\\testQueue". This means the queue will be created on the local machine with a queue name of testQueue.
Step Two. The label of a queue should contain a description of the queue being created. This can be as simple as an English description, or as complex as a GUID, depending on the use of the queue. In the sample code the pathname and label are the same.
Step Three. When the journal property is set to MQJOURNAL.MQ_JOURNAL, a copy of every message is voluntarily placed in the journal queue when the message is removed (this is discussed further in Step Eight). This is useful for logging or extended-message persistence. For example, if an application wants to refer back to a post-processed message, then it might be able to find it in the journal queue. However, it's up to the server application to purge the journal queue because the MSMQ system doesn't do it on its own.
Step Four. The type ID property of a queue is useful because it denotes that the queue is a member of a group of queues of a certain type. The only requirement for a type ID is that it must be a GUID. Each type ID must be unique, or else confusion and dangerous ambiguities between types would appear. However, there can be a varying number of queues with the same type ID. This feature is useful when a server or client wants to send a message to several queues of a certain type. A look-up mechanism that locates all queues of a certain type is demonstrated in the sample client code shown in Listing Two (beginning on page XX).
Step Five. Once the IMSMQQueueInfo object has all the necessary properties set, you can successfully create a queue. Creating a queue is a simple process, and the IMSMQQueueInfo.Create method only requires two parameters. The first parameter is a Variant of type boolean that simply denotes whether the queue is transactional or non-transactional. The second parameter is also a Variant of type boolean, but it designates whether people other than the creator of the queue can view or read existing messages in the queue. The default is set to false. This means that only applications running as the current user on the network are able to read from the queue. For the purpose of the sample server, this is security overkill. The second parameter has been set to true to show that anyone can read from the new queue.
It's important to note that in the sample code the Create method is wrapped by a try...catch structure. This is because a com.ms.com.COMFailException will be thrown if the queue already exists. Trying to create a queue that already exists returns an exception and doesn't harm existing queues.
Step Six. Now that the queue has been created, we need to open the queue and send a message. Opening the queue is performed using the IMSMQQueueInfo object. In the sample code, we reuse the same IMSMQQueueInfo object used to create the queue. Opening a pre-existing queue is done through the IMSMQQueueInfo.Open method. This method accepts two integer parameters. The first parameter denotes the type of access you want to the queue; there are three access types available (see Figure 9).
MQ_SEND_ACCESS |
Messages can only be sent to a queue. |
MQ_RECEIVE_ACCESS |
Messages can be peeked or received from a queue. |
MQ_PEEK_ACCESS |
Messages can only be looked at (peeked) in a queue. |
Figure 9: The three access types to the queue denoted by the first of two integer parameters.
The second parameter in the IMSMQQueueInfo.Open method denotes the type of sharing access you want to give to others trying to simultaneously perform receive actions against the queue. Although this is similar to file-access locking under the Win32 API, MSMQ locks the entire queue instead of just one message. When sending messages or peeking in the queue, the access must be set to MQSHARE.MQ_DENY_NONE. This means that locking the queue only applies to receivers. A receiver can lock a queue, causing any additional application that wants to receive messages to fail as long as the queue remains open for receiving (this doesn't interfere with sending or peeking applications). Receiver locking can be accomplished by passing the MQSHARE.MQ_DENY_RECEIVE_SHARE integer constant in as the second parameter. This form of locking isn't used in the sample code in this article, and all sharing is set to MQSHARE.MQ_DENY_NONE.
Step Seven. Once the queue is successfully opened, a message needs to be formulated (messages can be formulated at any time). The IMSMQMessage object will represent a message that can be put on the queue. The most important part of the message is the body property because that's where the content of the message is stored. This is a Variant object and it can store any type of data supported by the com.ms.com.Variant data type. In the sample server code, a Variant of type String is placed in the body property.
Step Eight. The new message is flagged as a message that should be placed in the Journal queue when it's removed from the application queue. This is important because, although the application queue was created with a journal, not all messages will automatically be logged in the journal queue. Messages must explicitly state that they want to be placed in the journal queue. This is done by setting a message's journal property to MQMSGJOURNAL.MQMSG_JOURNAL.
Step Nine. Now the message is ready to become part of a transaction and be sent to the queue. The sample code in this article uses internal transactions, because they're simpler and more efficient than external transactions. There are two Automation objects needed when using an internal transaction: the IMSMQTransactionDispenser object and the IMSMQTransaction object. Both are critical to the success of a transaction.
When creating an internal transaction, you must first create an IMSMQTransactionDispenser object. This object dispenses single transaction objects that can be used as internal MSMQ transactions. The new transaction is dispensed through a call to the BeginTransaction method found in the IMSMQTransactionDispenser object. A transaction object of type IMSMQTransaction is then returned. The new transaction object then needs to be wrapped in a Variant and included as part of the sending action.
Step Ten. The message that's been created is sent through a call to the IMSMQMessage.Send method, which accepts two parameters. The first parameter must be a reference to the queue object that represents the queue where the message is being sent. The second parameter needs to be a Variant that holds the wrapped transaction object. By placing the wrapped transaction object in the second parameter, MSMQ is able to perform the sending action as a part of the new internal transaction. Once the send call is completed, a subsequent method call to commit the transaction is required.
Step Eleven. Committing or aborting a transaction is an important part of internal transaction semantics. When a transaction is finished, a deliberate call needs to be made to the Commit method found in the IMSMQTransaction object. This method will commit changes to MSMQ and close the transaction. If anything fails, the transaction should be aborted. To this end, the sending and committing calls in the server source code have been wrapped with a try...catch structure. If an exception is thrown, the transaction will be aborted. When calling the Abort method, or the Commit method, the defaults are almost always used, causing a Variant of type noParam to be passed for each parameter.
Building a Simple Java Client
Building a simple client that retrieves messages from an MSMQ queue is even more trivial than building the server that sends them. This is because the client code opens a queue with one method and retrieves a message with another. Although a simple process, things can become more complex if you take advantage of the event mechanisms, and if you query for a large number of queues of a certain type. The sample client code shown in Listing Two demonstrates how to use events, and how to perform a query for a collection of queues of a certain type.
The sample client code uses eight of the 10 available Automation objects shown in Figure 8. First, the sample code shows how to look up and build a collection of queues of a certain type. Second, it shows how to use the com.ms.com.ConnectionPointCookie class to respond to an event from MSMQ. An event is fired whenever a message is placed in the queue. Finally, the sample code demonstrates how to retrieve a message through an internal transaction, and how to register to accept a new event.
Responding to COM events using Java can be problematic for the uninitiated. Although a description of the COM event mechanism deserves an article of its own, it's explained later in this article for use with MSMQ.
Connection Points
The SinkClass class shown in Listing Two is intended to be used with additional user interface code, and should not be run on its own. This is because, in order for the event mechanism to work, it's assumed that the client that requested the notification will be available when the event is fired. For example, if a client process registers for an event, but terminates before the event is fired, then the notification event that was to be sent to that client won't take place. That is why a persistent process must exist in order to respond to the event.
A sink is an important term associated with event notification. Java classes that respond to COM events are called sinks, and they need to implement the special COM interface defined in the type library for the connectable object. The SinkClass in Listing Two implements the mqoa._DMSMQEventEvents interface. The mqoa._DMSMQEventEvents Java interface defines the Arrived and ArrivedError methods the MSMQ system can call when an MSMQ event is fired.
When building a ConnectionPointCookie, an instance of the Java class that implements the special interface is passed as a parameter in the constructor of the com.ms.com.ConnectionPointCookie object. The ConnectionPointCookie object is the "glue" that maintains the connection between the client and the connectable object. For this reason, the actual reference to the ConnectionPointCookie object shouldn't exist as part of a function, but as a member variable in the Java class. This ensures the connection will remain open for the life of any instance of the Java class. This is covered in more detail later in the article.
Step-by-Step Discussion of the Client Source Code
Step One. The first function that needs to be called in the SinkClass object is the run method. The name "run" was chosen because the SinkClass object can be executed as a separate thread (if you change it to also implement the Runnable interface). It's up to the programmer to decide how to use the sample SinkClass code.
Step Two. When the run method is first called, it will perform a lookup across the network to build a collection of queues of a certain type. An object of type IMSMQQuery is required to perform this action. After the IMSMQQuery object is created, a single call to the IMSMQQuery.LookupQueue method will build the requested collection. The collection is contained in an IMSMQQueueInfo object that's explained later in this article. The IMSMQQuery.LookupQueue method accepts nine parameters and is, perhaps, the most confusing method of the MSMQ COM Automation objects.
The first parameter in this method can be either a GUID representing a single, specific queue on the system, or a Variant of type noParam. Each queue is assigned its own GUID when it's created. This helps uniquely identify each queue on the enterprise. This GUID is different from the type ID, because it's assigned on a per-queue basis, rather than a per-type basis. When a GUID is used in the first parameter, the returned collection will contain one queue, or no queues. It never has more than one queue because the GUID is unique to a single queue on the enterprise network.
The second parameter can be either a type ID, or a Variant of type noParam. A lookup, using this parameter as the main identifier, will return a collection of references to each queue of that type. The third parameter is used to find a queue or set of queues with a specified label. Collections built from only the third parameter aren't ensured to be of the same type unless a type ID is also specified in the second parameter.
The fourth and fifth parameters are used for retrieving a collection of queues that meet a certain time requirement. The fourth parameter returns collections of queues that were created at a specified time. The fifth parameter finds queues that were modified at a specific time. Modification takes place when the queue's properties were last set, the last time the IMSMQQueueInfo.Update method was called, or when the queue was created. The fourth and fifth parameters require a com.ms.com.Variant object of type Date that represents the specified time or a Variant of type noParam.
The remaining four parameters are relationship parameters. They're directly related to the second, third, fourth, and fifth parameters of the IMSMQQuery.LookupQueue method. Each one of these last four parameters can appear in the form of seven different integer constants that are defined in the mqoa.RELOPS Java interface. These parameters act as simple boolean comparison operators that are used with their paired LookupQueue parameter. Definitions for these seven integer constants are shown in Figure 10.
REL_EQ |
Equal-to |
REL_NEQ |
Not-equal-to |
REL_LT |
Less-than |
REL_GT |
Greater-than |
REL_LE |
Less-than or equal-to |
REL_GE |
Greater-than or equal-to |
REL_NOP |
Ignore / Nothing |
Figure 10: Integer constants defined in the mqoa.RELOPS Java interface.
Relationship parameters provide a powerful way of retrieving exact queue collections. They allow comparisons to be made that will help return the most accurate set of queues desired. The sample client code in Listing Two only makes use of two out of the seven available integer constants. The two that are used are the REL_EQ and the REL_NOP values. They are used the most and they retrieve an accurate collection of queues representing a certain type.
Step Three. Once the LookupQueue method has been called, it will return an IMSMQQueueInfos object. This object can iterate through a collection of queues that meet the criteria specified in the LookupQueue method. When the IMSMQQueueInfos object is obtained, it's important to call the Reset method. This method returns the cursor to the top of the collection list, so it can be iterated from top to bottom. A subsequent call to the Next method returns the first IMSMQQueueInfos object in the collection list. This object represents a specific queue that matched the criteria for the LookupQueue method in Step Two. If a call to the Next method returns null, then either the collection is empty, or the cursor is at the end of the list.
Step Four. The next step is to create a ConnectionPointCookie object that is set to the designated SinkClass member variable. The ordering of this step is extremely important. This should be noted when viewing the sample piece of Java code on Microsoft's Web site that explains how to use ConnectionPointCookie objects with MSMQ because it's misleading (Microsoft Knowledge Base Article ID: Q176815). The creation of the ConnectionPointCookie object must take place before any calls to the IMSMQQueue.EnableNotification method, or the event mechanism will be inconsistent and seemingly buggy. If this isn't done, you'll experience frustrating debugging sessions trying to understand why events are being dropped.
There's one constructor that can be used to create a ConnectionPointCookie object. This constructor accepts three parameters. The first parameter is an IMSMQEvent object. An IMSMQEvent object is used as a single event handler that is capable of supporting multiple queues. The IMSMQEvent object acts as a funnel for events fired from one or more queues. For example, if there is one or more queues of the requested type in the sample client code (again, refer to Listing Two), then each queue will be notified that the member variable msmqEvent will handle their events. The second parameter must be a reference to the Java sink class. Because the sample SinkClass code implements the mqoa._DMSMQEventEvents interface, a reference to itself will be used for this parameter. The third parameter must be a Class object representing the interface that is implemented by the sink class. The third parameter is a Class object representing the mqoa._DMSMQEventEvents interface.
Step Five. Once a ConnectionPointCookie has been created the queue(s) must be opened for receive, peek, or send access. After a queue has been properly opened, a call to the IMSMQQueue.EnableNotification method is required to start the event mechanism on a queue. The EnableNotification method accepts three parameters. The first parameter is an IMSMQEvent object. In the sample client code, a reference to the member variable msmqEvent is passed in. This registers the specified queue, so all MSMQ events will be funneled through the member variable to the appropriate sink class.
The second parameter is a Variant of type integer or noParam. The integer must represent one of three different integer constants found in the MQMSGCURSOR Java interface. For example, by setting the value to MQMSGCURSOR.MQMSG_FIRST, an event will be fired when a new message appears in the queue. The other two constants are more granular, and only request an event to be fired when the message appears at a specified cursor location. These constants (MQMSG_CURRENT and MQMSG_NEXT) aren't used in this article. In the sample client code, a Variant of type noParam was passed. This means the default constant, MQMSG_FIRST, will be used.
The third parameter is a timeout parameter. This parameter specifies how many milliseconds to wait for a message. Once the timeout has been reached, the event will fire an ArrivedError event that lets the application know the event has died. The sample code doesn't use this value. Instead, it sets it to a value of noParam. This means the event will wait infinitely for a message to arrive on the queue.
The Arrived and ArrivedError Events
There are two events supported by the MSMQ Automation objects. The first is the Arrived event, which is fired when a message is found in a queue. The second is the ArrivedError event, which is fired when receive errors, such as timeouts, are encountered. The sample SinkClass class implements the mqoa._DMSMQEventEvents interface so it can support these two events.
The MSMQ Arrived event consists of two parameters. The first parameter appears as an object of type Object, but it's really of type IMSMQQueue. The Object parameter needs to be cast to the appropriate IMSMQQueue type after the event is fired. This is shown in the sample client code in Listing Two. The second parameter is the current location of the cursor in the queue. This is used when events are fired based on the location of the cursor rather than the simple appearance of a message in the queue. MSMQ programmers will often ignore the cursor parameter because basic use of the event notification is used to notify the client that either a new message has arrived, or that messages exist in the queue. However, the cursor location can be used to perform advanced event firing.
When the Arrived method is called, a transaction is started and a message is pulled off the queue. If a message is successfully retrieved, then the transaction will commit and a Windows message box will display the body of the message. The message box comes from a call to the com.ms.wfc.ui.MessageBox.show (String message) method. The same method is used when a call to the ArrivedError event is fired. The sample code in this article is meant for testing purposes, so there is little logic in the ArrivedError method. Under normal circumstances this event is critical and should be used. Application users would feel confused if messages were put into a queue and only ArrivedError events were being fired, and the ArrivedError function didn't do anything meaningful.
At the end of the Arrived and ArrivedError methods, a call to the IMSMQQueue.EnableNotification method must be made to reregister to receive events on the specified queue. Once the event mechanism is triggered for the queue, it needs to be reset. If you don't want to reregister to accept new events on the queue, you can leave this line out of your code. However, the sample client code in this article is designed to respond to every message that appears in the registered queue(s). For this reason, a call to the IMSMQQueue.EnableNotification method has been made at the end of each event method.
Conclusion
This article has discussed how Microsoft Messaging Queue is well suited for distributed applications. This includes large enterprise environments with multiple platforms and differing architectures. MSMQ is an integral part of Windows NT Server, and it's available to those who have taken the time to install the Option Pack. MSMQ has several features required by enterprise programmers, including tight security, transactions, support for third-party systems, and a programmer-friendly API.
Building Java applications that make use of MSMQ is simple. This article has demonstrated how Java programs can be used as either a server or client to MSMQ queues. This includes performing queries to look up multiple queues of a special type and responding to MSMQ events that are fired when a message is placed on a queue.
If you combine MSMQ as a great middleware server with the power of Java and its ease of use, then you'll find that the two technologies make a great team.
Marcus Daley is a software engineer at Webridge, building Java applications for customer, partner, and channel management. He has been involved with Java development since its birth. His first major accomplishment with Java was in 1995 while working for Utah State University, where he pioneered research into 3D modeling applications written in Java and VRML. Later, he joined a startup GIS company based in Northern Utah where he developed Java-based intranet solutions for small businesses and local governments. Since then he has consulted several companies throughout Northern Utah, and worked together with many respected professionals in the Java community. Marcus lives in Portland, OR, and can be reached at marcus@mdaley.net.
Begin Listing One - MSMQServer.java
import com.ms.com.*;
import mqoa.*;
public class MSMQServer
{
/**
* The main entry point for the application.
*
* @param args Array of parameters passed to the
* application via the command line.
*/
public static void main(String[] args)
{
// This is the type ID that will be associated with the
// new queue.
String typeID =
"{2318798E-9A3C-11d2-81F9-00A0C9714E00}";
Variant variantTrue = new Variant(),
variantNoParam = new Variant(),
guid = new Variant(),
nop = new Variant((int)RELOPS.REL_NOP),
isEqual = new Variant((int)RELOPS.REL_EQ),
oneSecondDelay = new Variant(),
theBody = new Variant(),
transHolder = new Variant();
IMSMQQueueInfo queueInfo =
(IMSMQQueueInfo) new MSMQQueueInfo(); // STEP 1
IMSMQQueue queue = null;
IMSMQMessage message = new MSMQMessage(); // STEP 7
IMSMQTransactionDispenser transDispense =
new MSMQTransactionDispenser(); // STEP 9
IMSMQTransaction trans;
// Setting some variables.
variantTrue.putBoolean(true);
variantNoParam.noParam();
oneSecondDelay.putInt(1000);
queueInfo.putPathName(".\\testQueue"); // STEP 1
queueInfo.putLabel("testQueue"); // STEP 2
queueInfo.putJournal(MQJOURNAL.MQ_JOURNAL); // STEP 3
queueInfo.putServiceTypeGuid(typeID); // STEP 4
try
{
queueInfo.Create(variantTrue,
variantTrue); // STEP 5
}
catch (ComFailException except)
{
}
queue = queueInfo.Open(MQACCESS.MQ_SEND_ACCESS,
MQSHARE.MQ_DENY_NONE); // STEP 6
theBody.putString("This is a test");
message.putBody(theBody); // STEP 7
message.putJournal(
MQMSGJOURNAL.MQMSG_JOURNAL); // STEP 8
trans = transDispense.BeginTransaction(); // STEP 9
try
{
transHolder.putDispatch(trans); // STEP 9
message.Send(queue, transHolder); // STEP 10
trans.Commit(variantNoParam, variantNoParam,
variantNoParam); // STEP 11
}
catch (Exception transactionFailed)
{
trans.Abort(variantNoParam,
variantNoParam); // STEP 11
}
}
}
End Listing One
Begin Listing Two - SinkClass.java
import com.ms.com.*;
import mqoa.*;
public class SinkClass implements mqoa._DMSMQEventEvents
{
// The ConnectioPointCookie member is global so that it
// exists for the life of an instance of this class.
private ConnectionPointCookie cookie;
private IMSMQQueue queue;
private IMSMQEvent msmqEvent;
private Variant variantTrue = new Variant();
private Variant variantNoParam = new Variant();
public void Arrived(Object Queue, int Cursor)
{
IMSMQMessage message = null;
IMSMQTransactionDispenser transDispense =
new MSMQTransactionDispenser();
IMSMQTransaction trans;
Variant oneSecondDelay = new Variant();
Variant transHolder = new Variant();
OneSecondDelay.putInt(1000);
// Casting the Object parameter to the appropriate
// IMSMQQueue variable.
queue = (IMSMQQueue) Queue;
// Starting a transaction.
trans = transDispense.BeginTransaction();
try
{
// Wrapping the transaction object in a Variant.
transHolder.putDispatch(trans);
// Receiving a message transactionally.
message = queue.Receive(transHolder, variantTrue,
variantTrue, oneSecondDelay);
// Committing the transaction.
trans.Commit(variantNoParam, variantNoParam,
variantNoParam);
}
catch (Exception transactionFailed)
{
// Aborting the transaction because of a failure.
trans.Abort(variantNoParam, variantNoParam);
}
if (message != null)
{
// Displaying a Windows message box with the message
// body.
com.ms.wfc.ui.MessageBox.show((String)
message.getBody().toString());
}
// Resetting the event mechanism.
queue.EnableNotification(msmqEvent, variantNoParam,
variantNoParam);
}
public void ArrivedError(Object Queue, int ErrorCode,
int Cursor)
{
// Displaying a Windows message box with an error
// message.
com.ms.wfc.ui.MessageBox.show("Error!");
// Resetting the event mechanism.
queue.EnableNotification(msmqEvent, variantNoParam,
variantNoParam);
}
public void run() // STEP 1
{
// This is the type ID that will be associated with the
// existing queue(s).
String typeID =
"{2318798E-9A3C-11d2-81F9-00A0C9714E00}";
Variant guid = new Variant(),
nop = new Variant((int)RELOPS.REL_NOP),
isEqual = new Variant((int)RELOPS.REL_EQ);
IMSMQQueueInfo queryInfo;
IMSMQQueueInfos qInfos;
IMSMQQuery query = new MSMQQuery(); // STEP 2
guid.putString(typeID);
qInfos = query.LookupQueue(variantNoParam, guid,
variantNoParam, variantNoParam, variantNoParam,
isEqual, nop, nop, nop); // STEP 2
qInfos.Reset(); // STEP 3
queryInfo = qInfos.Next(); // STEP 3
try
{
cookie = new ConnectionPointCookie(msmqEvent, this,
Class.forName("mqoa._DMSMQEventEvents"));// STEP 4
}
catch (ClassNotFoundException cnfe)
{
}
while (queryInfo != null)
{
queue = queryInfo.Open(MQACCESS.MQ_RECEIVE_ACCESS,
MQSHARE.MQ_DENY_NONE); // STEP 5
try
{
queue.EnableNotification(msmqEvent, variantNoParam,
variantNoParam); // STEP 5
}
catch (ComSuccessException cse)
{
}
queryInfo = qInfos.Next();
}
}
public SinkClass()
{
// Creating the member event object and setting some
// variables.
msmqEvent = new MSMQEvent();
variantTrue.putBoolean(true);
variantNoParam.noParam();
}
}
End Listing Two