Using JMS from .NET

Introduction

Many organizations build enterprise backbones on top of asynchronous messaging infrastructures. Very often they choose a Java Message Service (JMS) implementation on which to build the erst of the system. The reasons for this are manyfold:

  • there are many competing JMS implementations (providers), many of them free.
  • JMS offers a simple yet powerful abstraction layer for asynchronous messaging.
  • most JMS implementations are pure Java, therefore very platform portable.

The only problem with JMS is that it is a Java technology and leaves all your applications written in .NET out of the picture. Some providers offer prorpietary product extensions that allow more or less (usually less) complete access to the messaging infrastructure from .NET. The problem with these vendor extensions is that they provide the ultimate lock-in. Once you've taken advantage of the vendor's proprietary .NET interface, you cannot simply switch out your JMS provider for another one without disabling your .NET application or necessitating a costly rewrite.

Architecture

JMS C++ architecture

As the image on the right shows, you have a messaging backbone to which a Java publisher and a listener are connected. This is the traditional picture for JMS-based applications.

The new factor in this design is the .NET participant in the top left corner of the picture. This application is written entirely in .NET, using JuggerNET-generated proxy types for the JMS API types. The .NET developer implements a MessageListener in .NET by implementing proxy interface for the JMS MessageListener interface.

Under the hood, completely hidden from the .NET developer, the proxy types and the Codemesh runtime load a JVM into the .NET process and delegate (purple connectors) to the underlying Java objects and, in this callback case, from the JVM back into the .NET process. All threading-related issues are totally transparent to the .NET developer because the JMS implementation takes care of kicking off the required housekeeping threads for the management of the backbone connection.

  1. The developer is writing C# or VB.NET or other .NET language code that happens to look a lot like Java code, but is otherwise blissfully unaware of the fact that there is any Java involved.
  2. The backbone is totally unaware that there is a .NET client connected to it. As far as it is concerned, it is communicating with a Java client, which happens to be "hosted" by a CLR process.
  3. You do not need any particular JMS provider features for this to work.

This kind of integration is just about as invisible and non-intrusive as possible.

Development process

By the time you're looking at Codemesh products, you typically already have a JMS provider in place, be it a stand-alone implementation or one bundled with an application server. Occasionally though, we have customers who have the luxury of not only picking our products for the .NET integration but also the JMS provider for the actual messaging. Regardless, whatever JMS provider you pick, it typically comes with a bundled jms.jar file containing the types described by the JMS specification. These are the only (core) types we need to expose. We're actively uninterested in vendor-specific JMS implementation types because using those types would tie us to the particular vendor's implementation.

You import the jms.jar file into the code generator and generate the proxy types. You will wish to enable several support types like InitialContext, some reflection API types, some collection types, and the primitive wrapper types to ensure maximum usability of the proxy API.

Once the .NET bindings have been generated (as source files), you can either build them into an assembly and use that assembly or you can add them directly to your .NET project, it's totally up to you. Regardless, you can now write C# code such as this (the lookup portions might differ for your JMS provider):

static void Main( string args[] )
{
    string                  queueName = null;
    Context                 jndiContext = null;
    QueueConnectionFactory  queueConnectionFactory = null;
    QueueConnection         queueConnection = null;
    QueueSession            queueSession = null;
    Queue                   queue = null;
    QueueSender             queueSender = null;
    TextMessage             message = null;
    int                     NUM_MSGS;
        
    if ( (args.Length < 1) || (args.Length > 2) ) 
    {
        System.Out.Println( "Usage: SimpleQueueSender <queue-name>"
" [<number-of-messages>]"); return; } queueName = args[ 0 ]; Console.WriteLine "Queue name is {0}" + args[ 0 ] ); if( argc == 2 ) NUM_MSGS = new Integer( args[ 1 ] )).IntValue(); else NUM_MSGS = 1; /* * Create a JNDI API InitialContext object if none exists * yet. */ try { jndiContext = new InitialContext(); } catch( NamingException e ) { Console.WriteLine( "Could not create JNDI API context: {0}",
e.ToString()); return; } /* * Look up connection factory and queue. If either does * not exist, exit. */ try { queueConnectionFactory = (QueueConnectionFactory) jndiContext.Lookup( "QueueConnectionFactory" ); queue = (Queue)jndiContext.Lookup( queueName ); } catch( NamingException ne ) { Console.WriteLine( "JNDI API lookup failed: {0}", e.ToString() ); return; } /* * Create connection. * Create session from connection; false means session is * not transacted. * Create sender and text message. * Send messages, varying text slightly. * Send end-of-messages message. * Finally, close connection. */ try { queueConnection = queueConnectionFactory.CreateQueueConnection(); queueSession = queueConnection.CreateQueueSession( false, SessionImpl.AUTO_ACKNOWLEDGE); queueSender = queueSession.CreateSender( queue ); message = queueSession.CreateTextMessage(); for( int i = 0; i < NUM_MSGS; i++ ) { string msgText = "This is message " + (i+1); message.SetText( msgText ); queueSender.Send( message ); } /* * Send a non-text control message indicating end of * messages. */ queueSender.Send( queueSession.CreateMessage() ); } catch( JMSException je ) { Console.WriteLine( "Exception occurred: {0}", e.ToString() ); } if( queueConnection != null ) { try { queueConnection.Close(); } catch( JMSException je) { } } }

If you know the Java original, you will recognize how similar that C# code is to it. There are just a few .NET'isms that you have to get used to, otherwise it looks immediately understandable and it is certainly highly maintainable.

Please also check out the JMS Courier Topic Publisher and Topic Subscriber examples written in C#.

Deployment

Your .NET application will require the Codemesh runtime library to be present and it will require a Java Runtime Environment (JRE). You can bundle a so-called "private" JRE with your application, or you can rely on the presence of a JRE on the client host. The latter alternative might be perfectly acceptable in well-controlled intranet deployment scenarios.

There are no configuration changes required to accommodate the backbone communications (assuming you already have things set up for Java backbone clients).

Summary

At first, it sounds strange to use in-process integration for as explicitly an out-of-process use case as messaging, but it has some huge advantages when compared with competing integration approaches. It has better performance, better security, easier deployment, and great maintainability. On top of that, you have JMS vendor independence. All of these factors combine for an excellent cost/benefit picture.


Copyright 2006-2011 by Codemesh, Inc., ALL RIGHTS RESERVED

:
jms .net
home products support customers partners newsroom about us contact us