Collaboration Data Objects: Using E-Mail in Your Application
Dr. Bruce E. Krell and Ken Miller
January 1999
Summary: Discusses how to incorporate e-mail into your client applications through the use of Collaboration Data Objects, a high-level set of COM objects that allow easy access to e-mail systems embedded in Microsoft® Windows® products. (14 printed pages)
Contents
Introduction
An Overview of CDO
A Few Important Implementation Details
Acquiring and Installing CDO
A Small Class for Easily Using CDO
An Application Test Driver
A Lot Of Gory Details
Summary
Introduction
Over several decades of software development, I have been asked to develop many applications that incorporated e-mail. An example of this type of system involves manufacturing control. Manufacturing control systems typically involve several primary activities:
- Monitoring manufacturing hardware
- Evaluating the status of monitored hardware
- Notifying interested engineers regarding status problems
Typically, problem notification consists of formulating and sending e-mail messages to all addresses on a recipient list.
In order to accomplish these tasks, the following software architecture was usually required.
The Status Monitor component has the responsibility for monitoring hardware. Status data is collected. This data is then compared to an acceptable set of ranges. If the data falls outside the required range, an e-mail message is formulated. This message is then distributed to every address on a stored recipient list. All messages are transmitted to the next component—a Mail Server. The Server holds these messages in a repository. When the Mail Client component requests the messages, the Server transmits all messages to the Client.
Now, in the olden days, I had to implement all these components myself. This required a lot of coding and took some time, as you might guess. However, because Microsoft Windows NT® now forms the basis of many control systems, I can use a somewhat different architecture. You see, Windows NT has available a powerful e-mail system that anyone can access from within their program. The architecture I can use these days looks more like the following.
Yes, instead of writing my own messaging system, I can now use the facilities of the Windows NT Exchange System for transmission on receipt of e-mail from within my application. The Monitor simply submits the e-mail to the Microsoft Outlook® client that resides on my local machine. If this client is connected to the Exchange Server, the message is automatically forwarded to the Server. If the client is not currently connected to the Server, the message is stored in the local mailbox. Messages stay in the mailbox until a user explicitly chooses to send the messages by executing the Send And Receive command provided by the Outlook client.
Initially, you could access the e-mail services of Windows NT through the Messaging API. I am sure that you have heard of this—MAPI. Unfortunately, the MAPI approach was a little tedious, because you were expected to program all of the low-level details for yourself. These days, however, your life is really easy when using the native e-mail system. Now you have a Component Object Model (COM) object called Collaboration Data Objects (CDO). As I will show you later, you can easily incorporate e-mail into your applications, such as my Status Monitor component, using the capabilities provided through CDO. Moreover, access to CDO is really easy when you use CDO from within Java.
An Overview of CDO
Collaboration Data Objects are a high-level set of COM objects that allow you to easily access the e-mail system embedded in the Microsoft Windows® product line. CDO objects are generally used by client applications. For the most part, CDO objects are used by clients wanting to access the e-mail system. Service providers must be implemented using the native MAPI library.
The best mechanism for introducing you to CDO objects is to correlate these objects to specific elements of the Outlook client user interface. Fundamental CDO objects you utilize in your client application include the following.
These objects exist within a structure that you access within your client program. The structure can be characterized as follows.
Higher-level objects within this structure contain the lower-level objects. This containment relationship has a very important effect on you as a programmer. In order to access an object at the lower level, you must access all of the immediate upper levels. So, to get an input message, you must execute the following sequence:
- Get a Session object.
- Get the Inbox Folder object from the Session object.
- Get the Messages Collection object from the Inbox Folder object.
- Get the Message object from the Messages Collection object.
Actually, all these gets are not really difficult to do inside Java. Access methods provided by the class definition of each object convert this into a simple method call. And, because you are working in Java, much of the low-level details for working with a COM object are effectively hidden inside the Java class implementation.
Note that a lot more objects exist than just these. You will see examples of some of the other objects in the code listings later. Additional documentation on the CDO objects and their usage can be found in the MSDN Library. Look under the following topic sequence: Platform SDK/Database And Messaging Services/Collaboration Data Objects.
A Few Important Implementation Details
Just for grins, let me show you the overall implementation architecture you are using when you employ CDO. The layer of libraries you utilize is as follows.
You might think, with all this layering of COM objects, that you are incurring a lot of overhead. Well, you are wrong. When you execute the sample code, you see that the code executes quite rapidly. This performance occurs because Microsoft has spent considerable time and effort to obtain high performance from COM objects.
CDO COM objects are implemented as dispatch interfaces. This approach is used to enable Microsoft Visual Basic® programs to access these objects. When you pass arguments to any method supported by a dispatch interface, you must package the argument in a special format. You may have seen this format before. The format goes by the name Variant data type. Typically, any return values come to your client application packaged into this data type. You must unpack the data in order to get at the real data.
Variant data types are not really mystical entities. Basically, this type allows you to represent a predefined set of data types in a format that is independent of the programming language. You simply set one field indicating the type of data, such as int. Then, the second field receives the actual int data value. The COM object can tell from the first field how to access the actual data.
Fortunately, this packing and unpacking is quite simple in Java. In keeping with the Java approach to encapsulation, you have available a package that provides a Variant class. The complete name for the class is com.ms.com.Variant. Methods associated with this class allow for easy packing and unpacking of data into and from a Variant format.
Let me show you a short example of packing a value into a Variant data type:
Declare a local variable or a field to hold a reference to the Variant object:
com.ms.com.Variant ProfileName ;
Create a Variant object using the new operator:
ProfileName = new com.ms.com.Variant() ;
Stuff data into the Variant object using available access methods:
ProfileName.putString(Profile) ;
Note Profile is a java.lang.String object initialized prior to this code.
Now, I know I told you earlier that Variant variables contain two fields. However, in this code, you only set one value, Profile. In Java, the encapsulation for the Variant type automatically sets the first field for you. After all, because the access method indicates the data type of the original data, setting the first field of the Variant variable is a mechanical process that you can allow the computer to handle for you. (Hooray, an intelligently handled abstraction!)
Different methods exist for inserting other data types, such as byte, int, float, and double. A complete set of methods is also available for extracting data, depending upon the stored data type. For more examples, see the following code listings; or, check the documentation that installs with Microsoft Visual J++®.
In fact, CDO objects are the elements of the CDO library that allow you to manipulate lower-level data structures in the messaging system. With the CDO library, you have direct access to all the data managed by the underlying e-mail system. If you want to provide a visual interface to the e-mail system from within your Web page, you must use another portion of the CDO library called the rendering library. The rendering library is stored in a file named cdohtml.dll. Using this library is outside the scope of this article.
Acquiring and Installing CDO
You can acquire CDO in one of several ways, for example:
- Windows 95/98/NT Workstation:Download Outlook 98.
- Windows NT Server:Download Exchange 5.5.
When you install Outlook 98, you should have cdo.dll, cdohtml.dll, and mapi32.dll installed onto your system.
When you install Exchange 5.5, you must take special steps in order to install these libraries. Choose Custom Setup. From this setup, select IIS/Active Server/Active Server Pages Option. This setup process causes the earlier DLLs to be installed on your system.
You may for some reason get stuck manually installing the CDO library. You can easily do this with the following statement: regsvr32 cdo.dll. Type this command from a DOS prompt. The regsvr32 is a utility that invokes self-registration routines embedded within an in-process COM object such as cdo.dll.
Once you install CDO on your machine, you need to generate a Java wrapper class for the CDO COM object. You can accomplish this by using the jactivex utility. The command for creating a Java class wrapper is the following:
jactivex" /javatlb /xi /d "destination folder" "c:\source folder\cdo.dll"
Yes, one or more spaces appears between /d and the start of the destination folder name. If any of folder names contain spaces, the complete path information must be surrounded by quotes.
An easier approach is to use the Visual J++ features for generating a COM wrapper class. Start a new project. Choose Project from the main menu. Then, select Add COM Wrapper from the pop-up submenu. These actions cause the following dialog box to display.
Click the empty square next to Microsoft CDO library. Click OK. The result jactivex is executed on the COM object DLL. A Java wrapper class is placed into a subfolder within your project folder. This subfolder automatically appears in your Project Explorer. You access the CDO classes through a new package named cdo (all lower case—remember, Java is case sensitive).
A Small Class for Easily Using CDO
Although CDO is much easier to use than direct MAPI, you still have a bit of tedious programming to use CDO. In order to make CDO usage easier for you, I have generated a small CDO wrapper class. A declaration for this wrapper class is as follows:
class ConnectionClass
{
private cdo.Session CurrentSession ;
private com.ms.com.Variant NullParameter ;
private cdo.Folder InBox ;
private cdo.Messages InMessageCollection ;
private cdo.Folder OutBox ;
private cdo.Messages OutMessageCollection ;
public ConnectionClass() ;
public void ConnectToServer( String Profile ) ;
public void SendMessageToServer( String SenderName, String ReceiverName,
String SubjectText, String MessageText) ;
public int DetermineNumberOfMessagesFromSender(String SenderName) ;
public void ReadMessageFromSender( String SenderName, int MessageNumber ,
StringBuffer SubjectText,
StringBuffer MessageText ) ;
public void DeleteMessageFromSender(String SenderName, int MessageNumber)
public void DisconnectFromServer() ;
public void ReviewAllMessages(int InOrOut,String SearchName) ;
}
Notice the plethora of private fields declared at the start of the class. This class maintains a single connection to the e-mail system. As you access the e-mail system through various CDO objects, references to these objects are automatically maintained for you in a meaningful way. Ah, the joys of using encapsulation in Java. By using this class, you can save significant development time when programming a client that interacts with the messaging system. Methods in this class implement most of the functionality you will need for basic messaging within your application. Of course, you can easily add methods to this class to incorporate capabilities such as attaching files to messages.
An Application Test Driver
First, let me show you how to use this class in a client application. As you might guess, you must use the methods in a specific sequence to assure that everything works correctly. This small driver program illustrates for you the correct sequence of method calls.
Can you believe how readable this stuff is? Even your boss will be able to read and understand what this code does. Writing readable code like this could actually start a new trend—bosses and customers who believe in your work and give you great raises.
Notice you do not see any of the details about the CDO objects being exposed to the rest of the program. The private fields within this class maintain references to the various CDO objects. Your application knows nothing about these objects.
Hiding or encapsulating all this stuff provides added reliability to your application. You debug the CDO details once inside this class. All other applications or sections of the application use this class. Therefore, this class becomes a building block for your application.
You can understand most of this code just by reading the code carefully, because the methods look and read like English. However, a few of the arguments to some of the methods need further explanation:
Connection.ConnectToServer("Preferred Customer") ;
The argument to this method must be a profile that is registered with your local Outlook client. If you use a name that is not registered, you are prompted to create one. At this time, you cannot create a profile programmatically using the CDO library. Programmatic creation of profiles is apparently a server-side operation that is unavailable through CDO.
Connection.SendMessageToServer( "70625.354@compuserve.com",
"swarch.krell@mci2000.com",
"Test Message",
"This Is A Test Message" ) ;
SenderName is the first argument to this method. This name must be a full e-mail address. The second argument, ReceiverName, also must be a full e-mail address. A String object defining the message subject is the third argument. Finally, the message body comprises the fourth argument. This argument must also be a String object.
A Lot Of Gory Details
I guess you would like to look at a few of these methods in detail. So, let's get on with the job:
public ConnectionClass()
{
CurrentSession = new cdo.Session() ;
NullParameter = new com.ms.com.Variant() ;
NullParameter.noParam() ;
}
This little gem is the constructor for my ConnectionClass. First, the constructor creates a Session object. The Session object is defined in your COM wrapper class named cdo. You may not realize this, but this usage of the new operator is special. When you use new to create an instance of an object from a wrapper class, the operator actually initializes COM for you and then creates an instance of the COM object. In C++, these actions require several lines of code with lots of arguments that you can just plain get wrong. Here, using Java, you let the Java Virtual Machine (JVM) do all the gritty work. After all, isn't this what computers are designed to do for you?
Next, the constructor creates a variant object named NullParameter. This object is set to a system value indicating that no parameters are provided. You set both fields of this object by invoking the method noParam:
public void ConnectToServer( String Profile )
{
com.ms.com.Variant ProfileName ;
ProfileName = new com.ms.com.Variant() ;
ProfileName.putString(Profile) ;
CurrentSession.Logon(ProfileName,
NullParameter,NullParameter,NullParameter,
NullParameter,NullParameter,NullParameter) ;
InBox = (cdo.Folder) CurrentSession.getInbox().getDispatch();
InMessageCollection = (cdo.Messages) InBox.getMessages().getDispatch();
OutBox = (cdo.Folder) CurrentSession.getOutbox().getDispatch();
OutMessageCollection = (cdo.Messages) OutBox.getMessages().getDispatch();
}
Recall the hierarchy of contained objects. Well, the goal of this method is to traverse through the hierarchy in order to cache references to the InBox, the InMessageCollection, the OutBox, and the OutMessageCollection.
So, you initially stuff the profile name into another one of those pesky Variant variables. You pass this to the Logon method provided by the Session object. This method attempts to log you onto the Exchange Server residing on the Windows NT Server that is registered for your client machine. This method also connects you to the local Outlook client. In this way, you can transmit messages to the local Outlook client if a server is unavailable. Also, you can read and delete messages currently stored by the local Outlook client.
You then traverse both the input message and the output message object hierarchies. First, you retrieve the InBox object from the Session object. Then, you request the InMessageCollection object from the InBox object. Both references are cached into private fields in the class. A similar sequence follows for the output message objects.
When you retrieve any of these objects using an access method such as getInbox, you are really obtaining a reference to a Variant object. So, to get the real Inbox object reference stored in this Variant, you need to call an access method of the Variant object. Because all objects in CDO are COM objects implemented as dispatch interfaces, you must extract a reference to a dispatch interface from the Variant object. You extract the dispatch interface reference through the getDispatch method. After all of this mishmash, you have a reference to a dispatch interface. You can now invoke methods supplied by the interface. So, when you invoke the getMessages method of the InBox object, you are actually executing a method provided by the underlying dispatch interface. However, unlike C++ programs, your program has a reference to the dispatch interface, not a pointer to the dispatch interface. You are receiving all of the protections of Java while using COM objects. Pretty neat, wouldn't you say?
public void ReadMessageFromSender(String SenderName, int MessageNumber ,
StringBuffer SubjectText,
StringBuffer MessageText )
{
cdo.MessageFilter SearchFilter ;
cdo.Message CurrentMessage ;
com.ms.com.Variant FilterName ;
com.ms.com.Variant Index ;
com.ms.com.Variant Subject ;
com.ms.com.Variant Text ;
String SubjectString ;
String TextString ;
FilterName = new com.ms.com.Variant() ;
Index = new com.ms.com.Variant() ;
SearchFilter = (cdo.MessageFilter) InMessageCollection.getFilter().getDispatch();
FilterName.putString(SenderName) ;
SearchFilter. setSender(FilterName) ;
Index.putInt(MessageNumber) ;
CurrentMessage = (cdo.Message) InMessageCollection.getItem(Index).getDispatch() ;
Subject = CurrentMessage.getSubject() ;
SubjectString = Subject.getString() ;
SubjectText.append(SubjectString) ;
Text = CurrentMessage.getText() ;
TextString = Text.getString() ;
MessageText.append(TextString) ;
}
In this method, you read a specific message from the list of messages from a specific sender. These messages form a subset of the total list of messages in the InMessageCollection.
The mechanism for targeting read operations on the InMessageCollection is to use a MessageFilter object. In fact, the InMessageCollection object contains a MessageFilter object. So, to limit all get operations to a specific sender, you simply set the MessageFilter object properties accordingly. Setting the message filter requires several steps:
- Retrieve the filter, converting the returned Variant into a dispatch interface reference.
- Pack the sender name into a Variant named FilterName.
- Set the Sender property of the filter using the Variant named FilterName.
In effect, these actions create a sublist containing only messages from the indicated Sender. Now you can obtain one of these messages. The process is admittedly a bit involved. You see, you obtain these messages by index number, an integer starting with 1, not 0. But, of course, you are sending the index number to a dispatch interface. So, before you can ask for a specific Message object in the sublist, you pack the index number into a Variant, which is named Index. The method that receives this Index Variant is the getItem method of the InMessageCollection object. Because you previously set the MessageFilter, you get a reference to a dispatch interface of the specific message in the targeted sublist of messages (after extracting the reference from the returned Variant, of course).
Now, you have a reference to a dispatch interface of a Message object. I guess you would like to access data in the individual fields or properties, such as Subject and Text. I bet you can even guess how to accomplish this. You call an access method whose name begins with get. This method returns a reference to a Variant. You then use a Variant method to extract the actual data. In the case of a property that is a String object, you must create a StringBuffer object in order to return the value to the caller of the method:
public void DisconnectFromServer()
{
try
{
CurrentSession.Logoff() ;
}
catch( Exception e )
{
System.out.println("Logoff Failed") ;
}
}
If you want to disconnect, you simply execute the Logoff method provided by the Session object. Because this method throws an exception if you attempt to log off from a Session object that you have not logged onto, you must catch the exception. If you do not catch the exception, your client program simply does not compile.
Notice that, throughout this code, you have created a number of COM objects using the new operator. However, you do not have to do anything to release these objects. Nor do you have to unload the COM system itself. The garbage collector inside the JVM is pretty intelligent. When the garbage collector recognizes that the COM objects are no longer needed, the JVM simply releases the objects for you. And, if no longer necessary, the JVM also unloads the COM system. Now, this type of garbage collection is really an intelligent approach to the usage of Java and COM together.
Yep, a few more methods exist than these, and you really can read these methods now, because you have a better understanding of how CDO works from inside Java.
Summary
As you read through the remainder of the code, remember the following concepts:
- Primary objects used for accessing the e-mail system through CDO include:
- Session
- Folder
- Messages Collection
- Message
- All CDO objects exist within a containment hierarchy.
- Traverse higher-level contained objects to access lower-level objects.
- All CDO objects are implemented as dispatch interfaces.
- All inputs to methods must first be packed into Variant objects.
- All outputs from methods must first be unpacked from Variant objects.
- An overload of the new operator performs all COM initialization for you.
- Intelligent garbage collection performs all COM termination for you.
- Using a small class to further simplify access to CDO shortens development schedule.
- Using a small class to further simplify access to CDO leads to readable code.
With this introduction and the small class, you are ready to begin full-fledged CDO implementations.
Dr. Bruce Krell (swarch.krell@mci2000.com) is Principal Architect for the Software Architects, Inc. He has more than 30 years of development experience in all types of target environments. His development areas of specialty include high-performance image and signal processing applications and distributed database applications. He has developed application-oriented courses in MFC programming, SDK programming, Win32 system services, COM/ActiveX/ATL, device drivers, and Java programming.