Toward mailnews Exchange Web Services support: SOAP calls
February 1, 2010 – 2:28 pmI’ve embarked on an effort to investigate adding support for Exchange server to the mailnews code. Although Exchange in Windows has traditionally used port 135-based protocols, my understanding is that the future for them is SOAP-based Exchange Web Services (EWS). As a first step, I wanted to get a basic SOAP library working in current mailnews code.
I considered a variety of approaches to this. One extension “Asertiva Thunderbird Extension for Sugar” uses the IBM/Prototype js library for SOAP access. Others recommended that I consider one of the open source SOAP libraries, such as a python-based library, or Apache’s AXIS2 library. Or that I cooperate with the existing project to provide an open-source method of accessing Exchange server.
But I’m not sure of how “open source” I want all of this to be. From my perspective, “open source” as a charitable activity is not successful. We all need to eat, and so the revenue model needs to be clear if a project is going to be more than a phase of life I am going through at the moment. So I would rather keep my options open until I understand that better. Anyway, that’s a long discussion which is beyond the scope of the current posting, which is supposed to be a status update.
I am still in an education phase, trying to understand SOAP and the related protocols, and to figure out what exactly I gain from using any existing library versus doing things more directly from the raw XML. So as both a trial and education step (and against the recommendation of others I might add) I’ve tried to update the old Gecko webservices extension to work in current Gecko 1.9.2, and to work with some current Microsoft SOAP protocols.
Rather than start with EWS, I started with the simpler BING search calls. I used existing Microsoft demos in C#, and could capture the communications using Wireshark to see what I was supposed to be sending and receiving.
Updating the abandoned webservices extension to Gecko 1.9.2
After testing webservices some under an old Firefox 2 build, I upgraded portions of the code to work under a current comm-central trunk build, using Gecko 1.9.2. My requirements are somewhat simpler than the original extension:
- Most importantly, my target is chrome-based extensions rather than browser code, so a lot of the security issues that FF folks worried about were not important to me.
- I had no interest also in allowing native JS creation of components, as I could rely on using .createInstance calls instead.
- At least initially, I am not supporting the reading of wsdl files, nor the associated automatic creation of proxy calls and interfaces. Instead, I read in a schema file, and generate my own code for each method. My understanding is that Microsoft, in their EWS libraries, also does not actually automatically generate method calls from a WSDL-based proxy, but follows this same approach of starting with the schema files.
That allowed me to avoid about half of the existing code, and focus on the /schema and /soap directories of the webservices extension.
Looking at my hg logs, it took one week of development time, and 17 patches to get to the point where I could write a unit test under Gecko 1.9.2, and confirm that I could create webservices components using a unit test. (I’m doing things in a test-driven development fashion, writing XPCSHELL unit tests to try out different features of webservices.)
Learning and testing Gecko webservices
Although there was a little old documentation around on Gecko webservices, ultimately I just needed to learn things the old fashioned way, reading the code and its interfaces and experimenting to see what worked. I took little baby steps, starting first with an XML schema primer and eventually working my way toward duplicating the functionality in some Microsoft Bing search demo C# code. This phase, starting from the first demonstration of loading of SOAP components under Gecko 1.9.2 through testing of encoding and decoding of a Bing search message, took about 2 weeks of coding and 23 patches, with the creation of 22 unit tests in the process.
The main issue that I had to deal with in the existing SOAP code is that it did not support maxOccurs>1 schema types, such as this one from the Bing schema:
<xsd:complexType name="ArrayOfNewsArticle">
<xsd:sequence>
<xsd:element minOccurs="0" maxOccurs="unbounded"
name="NewsArticle" type="tns:NewsArticle" />
</xsd:sequence>
</xsd:complexType>
I solved this by using an nsIArray to hold multiple values of the same element.
Sample Code
So now I can do a Bing search in an XPCSHELL test, and decode and test the results. I want to show some of the js that I use, to give some idea of the complexity (or lack thereof) of using this.
I create a class BingSearch, then encode some values to setup the search. A basic call for a search looks like this:
function run_test()
{
do_test_pending();
getNews = new BingSearch();
getNews.Query = "obama";
getNews.Options = ["EnableHighlighting"];
getNews.Sources = ["News"];
getNews.News = {Offset: 0, SortBy: "Relevance", Count: 1};
getNews.invoke(getNewsListener);
}
The “BingSearch” object is presumably what a sophisticated library would create automatically from the WSDL file. Instead, I create it by hand. Here’s my partial implementation, that does not support all of the allowed inputs to a Bing search, but works for my tests:
function BingSearch()
{
// defaults
this.Version = "2.0";
this.AppId = "<you get this from Microsoft for your application>";
this.Market = "en-us";
}
BingSearch.prototype = new BingBase();
BingSearch.prototype.invoke = function BingSearch_invoke(aSOAPResponseListener)
{
let parametersBag = objectPropertyBag({
Version: this.Version,
Market: this.Market,
Query: this.Query,
AppId: this.AppId,
Options: arrayPropertyBag("SearchOption", this.Options),
Sources: arrayPropertyBag("SourceType", this.Sources),
News: objectPropertyBag(this.News)
});
// soap message component
let soapCall = Cc["@mozilla.org/xmlextras/soap/call;1"]
.createInstance(Ci.nsISOAPCall);
let parameters = [];
parameters.push(new soapParameter("parameters",
parametersBag,
this._schema.getTypeByName('SearchRequest')));
soapCall.encode(Ci.nsISOAPMessage.VERSION_1_2,
'SearchRequest',
this._schema.targetNamespace,
0, null, // header blocks
parameters.length, parameters);
soapCall.transportURI = "http://api.bing.net:80/soap.asmx";
soapCall.encoding = this._encoding;
soapCall.asyncInvoke(aSOAPResponseListener);
}
const kSchema = {
file: 'data/bing20.xsd',
schemaNamespace: 'http://www.w3.org/2001/XMLSchema',
targetNamespace: 'http://schemas.microsoft.com/LiveSearch/2008/03/Search'
}
function BingBase()
{
if (typeof this._schema == "undefined")
{
this._schema = getSchema(kSchema);
// use the 2001 SOAP encoder
this._encoding = Cc["@mozilla.org/xmlextras/soap/encoding;1"]
.createInstance(Ci.nsISOAPEncoding);
this._encoding.getAssociatedEncoding("http://www.w3.org/2001/09/soap-encoding", true);
this._encoding.schemaCollection = this._schema.collection;
}
}
So far, the complexity of this does not seem unmanageable to me. I’ve only shown the endoding step. Decoding the response consists of a creating a call-specific translator similar to the “BingSearch.prototype.invoke” function above, which relies on the webservices soap library decode method. All of the other functions I’ve created (such as arrayPropertyBag) are not at all specific to the nature of the SOAP interface being used. I am not seeing the need to process the WSDL file automatically and generate proxy functions.
I’m not yet convinced that this resurrection of the old webservices library is the right approach, but I am not seeing any obstacles to using it either. I can generate and decode soap calls fairly efficiently, debug issues that arise, plus I have code that will integrate fairly easily with either javascript or C++ code in a Gecko chrome environment.
Next steps
I’m trying to decide on the next step toward moving this forward. I’m leaning toward attempting a specific EWS application, such as read-only access to an Exchange calendar as a Lightning extension. Another option might be to add some core mailnews hooks to allow me to create either message accounts or addressbooks using an extension – though I’m hoping jcranmer will beat me to it.
Great work in progress!
If I may make a suggestion:
can you add to the next steps also the ability to access Public Folders (or Sharepoint)?
I’d certainly like to support that in the future. “Next steps” though is trying to figure out what is the easiest possible thing that I could do which would exercise EWS Soap calls in a Gecko environment. I doubt if Public Folders are any easier than normal folders, plus I am less familiar with the Mozilla code that might support that, so my first reaction is that that does not make a good next step for me.
If you want true exchange support, why not use the http://www.openchange.org/ native MAPI support?
openchange is the open-source option that has been suggested to me before. There are a variety of reasons why I am not looking at this initially.
First, as I understand it the direction that Microsoft is moving is away from MAPI and towards EWS as “true exchange support”. Their Outlook for Mac (Entourage), for example, as I understand it, never used MAPI, and is being rewritten to rely completely on EWS. As an new effort, I would rather code to the future rather than the past. Your posting implies that EWS is not real Exchange support, but I don’t think that is correct for the future.
Second, openchange does not list Windows as supported, while this is important to TB’s user base.
Third, openchange is under a GPL license. As I said in my posting, I’m not ready to commit myself to that path at this point in time.
(Leni wanted to post this, but he had troubles so sent by private email instead):
When I first began using SOAP I didn’t understand the difference between a document-oriented SOAP api and an RPC-style API.
What I learned was that clients of a document-oriented API get marginal value from a SOAP library in exchange for complexity. Most of the work is in:
* understanding the document structure and relationships
* parsing and building documents
If Exchange is going with a document-oriented API (like Zimbra did), you might be better off building special-purpose helper libraries on top of XMLHttpRequest. That’s what zindus did when it switched away from the webservices extension in march 08 and it was definitely the right decision.
Also, the webservices extension has more than it’s fair share of edge-case bugs.
Response to Leni:
I’m in the process of trying to understand SOAP better myself, but from my current understanding I would characterize the EWS SOAP as RPC style.
“you might be better off building special-purpose helper libraries on top of XMLHttpRequest”
I am only using a fraction of the webservices extension, and currently plan to build special-purpose helper libraries on top of that. The portion of the webservices extension I am using translates to/from XML into an intermediate format, where information is organized into nsIPropertyBag objects (and I have added some nsIArray to the mix). I still have to have another layer that will translate that to/from the Mozilla mailnews formats. I guess what you are saying is that I can use XML tools like XPath to do some of those same operations. I’ll have to give that a try as well before I get too committed to my current path.
“the webservices extension has more than it’s fair share of edge-case bugs” and so do other SOAP libraries. For example, I read that the AXIS2 library does not work with Exchange Server Web Services. If I am going to fix bugs, I would rather do it in the Mozilla environment at this point in my experience. And I am committed to doing any fixes that I need to the webservices code.
[...] my last report, I was testing and updating the old Mozilla SOAP framework for use in communicating with Exchange [...]
[...] integration project needed another layer in the architecture. Previously, I have discussed a layer that does SOAP calls, and a second layer that extends Mozilla mailnews objects. (Let me call the Mozilla-specific world [...]