Exchange Web Services
Exchange Web Services is a moderately incorrect implementation of a SOAP service. It appears to be possible to interact with Exchange using the SOAP framework in Java but there are some difficulties.
This set of pages will describe the process of implementing a synchronization engine for bedework which I hope will synchronize calendars between the two systems.
This is the mechanism we will use. Briefly, send a message to Exchange subscribing to a push feed for a given resource and Exchange will send back messages describing changes to the resource.
We have to consider a number of cases when (re)subscribing.
- First time subscription for this user to the resource
- Our service is restarting
- The other end died and we are trying to restart the subscriptions.
When subscribing, a status frequency in minutes is supplied. This will allow us to detect that Exchange has stopped talking to us. If we don't get any notification within the status frequency (plus some extra for delays in the system), then it appears the other end is down. A good guide is that a delay of twice the status frequency indicates trouble. Note that notifications of changes are sent (almost) immediately. The status frequency is essentially a minimum poll time.
When restarting a subscription we make use of the watermark last received. Supplying this on the resubscribe should ensure we miss no notifications. Somewhere in the MS documentation I believe it suggests that old watermarks can cause efficiency issues so treating those as a new subscription would make sense. However, really old watermarks means our service wasn't running for some time. There is a suggestion that after 7 days watermarks expire in some way.
Exchange has a complex process for retrying notifications. It will double the status frequency and try again on failure. It does this a small number of times then gives up. It's not clear (yet) what happens if the bedework service goes down then restarts while Exchange is still pinging us. The best approach is probably to persist all subscription states and reread them on restart. We don't need to special case a service restart, it just looks like a long delay since Exchange last contacted us.
This requires a web server which acts as the target for the notifications from exchange. We are building this whole engine as a single ear file to deploy on jboss. It will include the Exchange Synch service and a web server.
1. Build a jar of generated classes from the WSDL
Download the Exchange Web Services SDK and extract the folder EWSReference containing
Services.wsdl messages.xsd types.xsd
(or obtain them from your local exchange)
Place EWSReference at the same level as the metro bin directory.
Edit Services.wsdl and before the final closing tag add this
<wsdl:service name="ExchangeWebService"> <wsdl:port name="ExchangeWebPort" binding="tns:ExchangeServiceBinding"> <soap:address location="https://your.host/EWS/exchange.asmx"></soap:address> </wsdl:port> </wsdl:service>
In types.xsd, near the beginning, replace
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
cd EWSReference mkdir gensrc mkdir classes ../bin/wsimport.sh -d classes -s gensrc Services.wsdl
You should end up with a lot of java in gensrc and the compiled classes in classes. Within gensrc are two packages:
com.microsoft.schemas.exchange.services._2006.messages and com.microsoft.schemas.exchange.services._2006.types
Build a jar file from the results
$JAVA_HOME/bin/jar cf "exchangews-2010.jar" -C classes com
Create an exchange directory in jboss ROOT.war directory and copy in
types.xsd messages.xsd Services.wsdl
These are needed for the service later on
Tried the following change - then removed it
<wsdl:binding name="ExchangeServiceBinding" type="tns:ExchangeServicePortType" xmlns:wsoap="http://www.w3.org/ns/wsdl/soap" wsoap:version="1.2" wsoap:protocol="http://www.w3.org/2006/01/soap11/bindings/HTTP/">
From types.xsd we have these schema segments:
<xs:complexType name="BaseSubscriptionRequestType" abstract="true"> <xs:sequence> <xs:element name="FolderIds" type="t:NonEmptyArrayOfBaseFolderIdsType" /> <xs:element name="EventTypes" type="t:NonEmptyArrayOfNotificationEventTypesType" /> <xs:element minOccurs="0" name="Watermark" type="t:WatermarkType" /> </xs:sequence> </xs:complexType> <xs:complexType name="PushSubscriptionRequestType"> <xs:complexContent mixed="false"> <xs:extension base="t:BaseSubscriptionRequestType"> <xs:sequence> <xs:element name="StatusFrequency" type="t:SubscriptionStatusFrequencyType" /> <xs:element name="URL" type="xs:string" /> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType>
Folder ids appears to be a list of names - in our case a single calendar.
EventTypes is a list with entries described by
<xs:simpleType name="NotificationEventTypeType"> <xs:restriction base="xs:string"> <xs:enumeration value="CopiedEvent" /> <xs:enumeration value="CreatedEvent" /> <xs:enumeration value="DeletedEvent" /> <xs:enumeration value="ModifiedEvent" /> <xs:enumeration value="MovedEvent" /> <xs:enumeration value="NewMailEvent" /> </xs:restriction> </xs:simpleType>
We want all except NewMailEvent.
3. Prepare jboss
This should be done anyway - even if not running this service - unless you're trying to run under java 5.
Remove all the jbossws-native*.jar files from the common/lib and lib/endorsed. They should not be there for java 6. To handle an issue with EWS not handling content type correctly add a number of files to lib/endorsed to upgrade JAXB etc to 2.2.1. lib/endorsed should now contain:
activation.jar gmbal-api-only-2.2.1.jar jaxb-api-20100511-2.2.1.jar jaxb-impl-2.2.1.jar jaxws-api-2.2.1.jar jaxws-rt-2.2.1.jar jaxws-tools-2.2.1.jar policy-2.2.1.jar resolver.jar serializer.jar stax-api.jar stax-ex-2.2.1.jar streambuffer-2.2.1.jar xalan.jar xercesImpl.jar
The endorsed directory is available zipped up at http://bedework.org/downloads/lib/jboss5-1-0-lib-endorsed.zip
If the exchange server has self-signed certs we need to create a keystore. Get the certificate in a file - e.g. exchange.cert
$JAVA_HOME/bin/keytool -importcert -file exchange.cert -keystore <quickstart>/jboss-5.1.0.GA/server/default/data/bedework/exsynchcerts Enter keystore password: ... Re-enter new password: ... Trust this certificate? [no]: yes Certificate was added to keystore
5. Creating a subscription.
We're creating a push subscription based service. We start with a list of current subscriptions and subscribe to Exchange with each one. Each subscription will cause Exchange to call back to an http end-point created as part of our service. Each call-back tells us if anything changed.
The stub creation phase generated an ObjectFactory class and we call its methods to obtain Jaxb classes representing elements of the request
ObjectFactory of = new ObjectFactory(); ...
6. Deal with Exchange not handling content-type correctly
Exchange complains with
'text/xml;charset="utf-8"' was not the expected type 'text/xml; charset=utf-8'
clearly not handling the (valid) lack of space and/or quotes.
To get around this installed jaxb-2.2.1 and jaxws-2.2.1 in jboss endorsed. (2.1 is the current version in the runtime.)
Gained some information from