Mocking Exchange-server using Spring Boot/Java

Jakob Rahr Bork Jensen
6 min readJun 24, 2023

--

TL:DR: Below I will shortly introduce the challenge I faced. Then I will go through how you can mock Exchange-server step-by-step.

Situation

Recently, I struggled to integrate a case-handling system to an on-prem Microsoft Exchange-server. The idea was for the system to be able to post calendar appointments into the personal calendars of employees and rooms alike.

At my disposal I had access to an actual on-prem installation of an Exchange-server, although only by going through a VPN connection. This meant that I could test my implementation manually, but automated end-to-end tests were crippled. And worse, so was the developer experience!

In modern software development, the ability to ramp up your own local copy of the entire environment is crucial to enable development at speed

Without a mock-instance of the Exchange-server I would be required to be connected to the Exchange-server through a VPN connection in order to test all sorts of functionalities from here on out.

So what to do? Today, most external software systems can be easily added to your environment through the use of pre-built docker-containers or set up through Testcontainers. However, this was not the case with Exchange-server, that seemed to be only available as a full install on a Windows Server. Damn!

I decided to build a new (mock) microservice mocking Exchange-server. I was not able to find much documentation or guides for doing this — hence this article, in case you end up in the same situation.

Mocking Microsoft Exchange-server

Below you find the technical details regarding my case:

You can follow the steps below no matter which programming language you use, although some parts will be specific to Spring Boot/Java. Be aware, that you need access to an actual Exchange-server to retrieve the correct WSDL/XSD schemas for your situation. Also, if you use .NET, be aware of a similar EWS SDK for .NET.

The EWS Java SDK makes it possible to write all functionality in Java. The SDK then handles the transformation from Java to the SOAP XML that Exchange-server uses for communication with the outside world. When constructing the mock-service, we need to understand the exact schemas of this SOAP XML. Luckily, these are described in a WSDL file and two XSD schemas all published by the Exchange-server instance:

  • WSDL: http://<yourclientaccessserver>.com/ews/services.wsdl
  • Messages XSD: http://<yourclientaccessserver>.com/ews/messages.xsd
  • Types XSD: http://<yourclientaccessserver>.com/ews/types.xsd

Import and build POJOs from WSDL and XSDs

Once you have downloaded the WSDL and XSD schemas, import these to your project in whatever location you desire. Below is our example as a separate import folder:

In my case, I ran into an error in the types.XSD file with an unknown xml.xsd schema location. As you can see in the picture above, I solved this by adding my own version of this file, as this:

<?xml version='1.0'?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xml:lang="en">
<xs:attribute name="lang">
<xs:simpleType>
<xs:union memberTypes="xs:language">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value=""/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
</xs:schema>

With access to these schema files, the next job is to automatically generate POJOs. As I was using Gradle, I configured this in the build.gradle file. Similar functionality could be set up in Maven or other tools. Be aware that this files specifies the location of your WSDL and XSD files:

import io.mateo.cxf.codegen.wsdl2java.Wsdl2Java

plugins {
id 'org.springframework.boot' version "2.7.12"
id 'io.spring.dependency-management' version '1.1.0'
id 'java-library'
id "io.mateo.cxf-codegen" version "1.0.2" // https://ciscoo.github.io/cxf-codegen-gradle/docs/current/user-guide/
id 'maven-publish'
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'jakarta.xml.ws:jakarta.xml.ws-api:2.3.3'
implementation 'jakarta.jws:jakarta.jws-api:2.1.0'
implementation 'jakarta.annotation:jakarta.annotation-api:1.3.5'
implementation 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'
api 'org.apache.cxf:cxf-rt-frontend-jaxws:3.5.5'
implementation 'org.apache.cxf:cxf-rt-transports-http:3.6.1'
cxfCodegen 'jakarta.xml.ws:jakarta.xml.ws-api:2.3.3'
cxfCodegen 'jakarta.annotation:jakarta.annotation-api:1.3.5'
cxfCodegen 'ch.qos.logback:logback-classic:1.4.8'
cxfCodegen 'org.apache.commons:commons-text:1.10.0'

testImplementation 'org.xmlunit:xmlunit-core:2.9.1'
testImplementation 'org.xmlunit:xmlunit-matchers:2.9.1'
}

configurations.cxfCodegen {
resolutionStrategy.eachDependency {
if (requested.group == "org.apache.cxf") {
useVersion "3.5.5"
}
}
}


tasks.register("generate", Wsdl2Java) {
toolOptions {
wsdl.set(file("imported/wsdl/Exchange.wsdl"))
outputDir.set(file("$buildDir/generated-java"))
markGenerated.set(true)
extendedSoapHeaders.set(true)
}
}

task packageTests(type: Jar) {
classifier 'tests'
from sourceSets.test.output
}

test.dependsOn packageTests

tasks.named("compileJava").configure {
dependsOn("generate")
}

sourceSets {
main {
java {
srcDirs = ['src/main/java', "$buildDir/generated-java".toString()]
}
resources {
srcDirs = ['src/main/resources']
}
}
}

artifacts {
archives packageTests
}

Now, building the project will generate the POJO classes in the build directory:

Within both the messages and types packages you will find a sh..load of classes, as the WSDL and XSD schemas are very exhaustive. The class of your primary interest lies within the messages package, named ExchangeServicePortType, which is also the only generated Interface.

Implementering a mock-response

In my case, I wanted to respond to a call creating a new appointment in the calendar. The client code for making such a call could look something like this (for brevity try/catch etc. is removed from the example):

Appointment appointment = new Appointment(exchangeService);
appointment.setSubject("my subject");
// ... further setup of the appointment entity

appointment.save(SendInvitationsMode.SendOnlyToAll);

Upon calling the save-method, a call is made to the Exchange-server, which we will now mock. To do so, you need to implement the above mentioned ExchangeServicePortType-interface in the mock-application.
Be aware, that the interface is huge, and as such, a sh..load of methods will have to be overridden even though you might not want to mock all of them.

Figuring out which method to mock out can be a bit tricky given the many possibilities. Probably, the easiest option is to make all methods throw exceptions and then follow the stacktrace as your call fails.

Creating a new appointment triggers the createItem method:

@Service
public class ExchangeMockService implements ExchangeServicePortType {

@Override
public void createItem(CreateItemType request,
ExchangeImpersonationType impersonation,
MailboxCultureType mailboxCulture,
RequestServerVersion requestVersion,
TimeZoneContextType timeZoneContext,
Holder<CreateItemResponseType> createItemResult,
Holder<ServerVersionInfo> serverVersion) {
}

// .. override all other methods
}

Now, looking at the method a few insights are worth mentioning. Like all other generated methods of the WSDL, it returns void, although a response is made to the client. This is because response headers and body is created from the last two input parameters, the holder objects.

The ServerVersionInfo is transformed into a SoapHeader, while the CreateItemResponseType is transformed into the body. Now, in my case, all that the client needs is an appointmentId, that I can save clientside in case I would later alter or delete the appointment.

To understand what a possible response could look like, the method and its SOAP/XML structure can be looked up. For instance, a successful createItem call could look like this:

<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<t:ServerVersionInfo MajorVersion="8" MinorVersion="0" MajorBuildNumber="685" MinorBuildNumber="8"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap:Header>
<soap:Body>
<CreateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:CreateItemResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:Items>
<t:CalendarItem>
<t:ItemId Id="AAAlAFV" ChangeKey="DwAAABYA" />
</t:CalendarItem>
</m:Items>
</m:CreateItemResponseMessage>
</m:ResponseMessages>
</CreateItemResponse>
</soap:Body>
</soap:Envelope>

Below you find my example of how a similar answer could look in the code:

@Override
public void createItem(CreateItemType request, ExchangeImpersonationType impersonation, MailboxCultureType mailboxCulture,
RequestServerVersion requestVersion, TimeZoneContextType timeZoneContext,
Holder<CreateItemResponseType> createItemResult, Holder<ServerVersionInfo> serverVersion) {
// Construct the soap header
serverVersion.value = getServerVersionInfo();

// Construct the soap body
var successMessage = new ItemInfoResponseMessageType();
successMessage.setResponseCode("NoError");
successMessage.setResponseClass(ResponseClassType.SUCCESS);
successMessage.setItems(getItemArray(getCalendarItem()));

var responseMessagesArray = new ArrayOfResponseMessagesType();
var responseMessages = responseMessagesArray.getCreateItemResponseMessageOrDeleteItemResponseMessageOrGetItemResponseMessage();

QName qname = new QName("http://schemas.microsoft.com/exchange/services/2006/messages", "CreateItemResponseMessage");
responseMessages.add(new JAXBElement<>(qname, ItemInfoResponseMessageType.class, successMessage));

var responseBody = new CreateItemResponseType();
responseBody.setResponseMessages(responseMessagesArray);
createItemResult.value = responseBody;
}

private static ServerVersionInfo getServerVersionInfo() {
var responseHeader = new ServerVersionInfo();
responseHeader.setMajorVersion(8);
responseHeader.setMinorVersion(0);
responseHeader.setMajorBuildNumber(595);
responseHeader.setMinorBuildNumber(0);
return responseHeader;
}

private static ArrayOfRealItemsType getItemArray(ItemType... items) {
var itemArray = new ArrayOfRealItemsType();
var itemList = itemArray.getItemOrMessageOrSharingMessage();
itemList.addAll(Arrays.asList(items));
return itemArray;
}

private static CalendarItemType getCalendarItem() {
var item = new CalendarItemType();
item.setItemId(getItemId());
return item;
}

private static ItemIdType getItemId() {
var itemId = new ItemIdType();
itemId.setId(UUID.randomUUID().toString());
itemId.setChangeKey(UUID.randomUUID().toString());
return itemId;
}

Now your response is set up. All you now need is for the mock-application to be able to spin up and publish its endpoint for the client to call. To do this, we will create a configuration class.

The below configuration class basically just specifies the path of you endpoint along with setting up logging, that will make the application log out the SOAP requests and responses, making it easier for you to debug your mock responses:

@Configuration
public class ApplicationConfig {

@Bean
public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
return new ServletRegistrationBean<CXFServlet>(new CXFServlet(), "/api/*");
}

@Bean
@Primary
public DispatcherServletPath dispatcherServletPathProvider() {
return () -> "";
}

@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus(LoggingFeature loggingFeature) {
SpringBus cxfBus = new SpringBus();
cxfBus.getFeatures().add(loggingFeature);
return cxfBus;
}

@Bean
public LoggingFeature loggingFeature() {
LoggingFeature loggingFeature = new LoggingFeature();
loggingFeature.setPrettyLogging(true);
return loggingFeature;
}

@Bean
public Endpoint endpoint(Bus bus, ExchangeMockService exchangeMockService) {
EndpointImpl endpoint = new EndpointImpl(bus, exchangeMockService);
endpoint.publish("/mock");
return endpoint;
}
}

Given that your application spins up on port 8080, you can now call your Exchange-server mock-application on http://localhost:8080/api/mock

That’s it. I hope you found it useful.

--

--

Jakob Rahr Bork Jensen

Jakob is working as a Management Consultant within IT at Deloitte Consulting. Primarily works with IT-development & -architecture, Cloud, DevOps and Low Code.