Exchange Web Services Example – Part 2 – Creating Appointments

In part 2 of this series, we are going to spend some time looking at the CalendarItem API, how it works and what it takes to create, get and delete an appointment. Before we start, you should make sure you have your environment set up per the explanation in part 1. If you have done that, you can download the example code for both the Java portion and the OpenEdge portion so you can follow along as we walk through this code together.

Goals and Background

Part 2 of this series of articles is dedicated to giving you an overview of the process of connecting to Exchange and doing some basic Calendar item work. Through the sample code, you will learn how to:

  1. Connect to the Exchange Web Service from Java;
  2. Create calendar items on the Exchange Server;
  3. Get calendar items from the Exchange Server;
  4. Delete calendar items from the Exchange Server; and
  5. Connect to the Java service from OpenEdge to perform the same operations.

The Java functionality will be exposed as a Web Service for other platforms to leverage, which is how we will get at it from OpenEdge. The OpenEdge code leverages the new GUI for .NET and object-oriented extensions, so you may find the example interesting if you are an OpenEdge developer who has not done this before.

One of the things that I have learned about integration through bitter, painful experience, is that it is much easier if you remove as many variables as possible, so, for the purposes of this exercise, I am only going to expose a few of the calendar properties. This helps to keep the code simple and this article reasonably short. I think I succeeded at the former, but the latter… not so much.

The code that you will be looking at is code that I wrote as part of my research into the EWS API and, as such, it is prototype code. What this means is that it is ugly. I have not spent a lot of time commenting it and it does not resemble the code that I would deploy to production from an object-model point of view. But that is OK. A prototype is intended to prove a concept.

There is nowhere that integration is harder than when you try and integrate Microsoft's technologies with a non-Microsoft platform. Microsoft seems to make it deliberately difficult. I have therefore deliberately hand-coded the SOAP messages that are used to communicate with Exchange in this example. I started out by trying to use Java frameworks like Axis 2 and JAX-WS for the SOAP piece, but I quickly ran into problems. So I decided that I would remove those variables and prove I could get the code working. Now that I am satisfied that I can do what I need to be able to do, I will go back and decide what I am going to do about integrating one of these frameworks into the mix. 

Another important factor that I should point out about this prototype is that I plan to integrate this whole solution with an Enterprise Service Bus (specifically, Apache ServiceMix). So it is really important to me that this can function as a properly service-oriented solution – hence the focus on web service enablement. Ultimately the service will generate messages that can be captured and interrogated in the service bus using a complex event processing engine.

Setting up the Code

Showing line numbers in EclipseAssuming you followed my instructions in part 1and now have the environment working, you can download the code for this article. The zip file contains two projects:

  • EWSAPI – This is an Eclipse Java EE  project that is intended to be deployed to a Glassfish server. It contains the Java code that does the actual communication with Exchange and accepts WebService requests from external sources.
  • OEEWS – This is an Eclipse project that is intended to be deployed in an OpenEdge Architect workspace that calls the Java WebService.

Both of these projects can be imported in the same way as I described in part 1, under the heading "Running a quick test to prove it works", although you should be aware that EWSAPI should be imported into a Java EE workspace, whereas OEEWS should be imported into an OpenEdge Architect workspace. I have had problems trying to run OE Architect in the same workspace as a Java EE configuration, so I don't recommend you try it.

After you have imported EWSAPI into your Java EE workspace, go ahead and drag it across to the Glassfish server in the server view and it will be deployed for you, ready to run.

The OEEWS project does not require any special configuration. Just import it into OpenEdge Architect and you will be good to go. Note that this code was written and compiled under 10.2B01 so I don't know how compatible it is with earlier versions.

Turn on line numbers

In the remainder of this series of articles, I will be cross-referencing the code that I have provided in the zip file. When I do so, I will mention specific line numbers that map to the line numbers in the source file. It will help you a lot if you turn the line number feature on for all editors in Eclipse so that you can follow along.

Also note that I occasionally include portions of the source code in the article for easy reference, but when I do, I do not include the line numbers so the line number reference refers to the example code.

To turn line numbers on, follow these steps:

  1. From the Window menu, choose "Preferences"
  2. Expand the "General->Editors" nodes and select "Text Editors"
  3. Make sure the "Show line numbers" check box is checked as in the screenshot at left.

Understanding Code Contents

As mentioned, the code for this first article is divided into a Java EE project and an OpenEdge project. The two projects will grow over this series of articles. Here's a brief description of these two projects and how they relate to the rough architecture that I am working on.

Deployment architecture

EWSAPI – Exchange Web Services API

The EWSAPI project is a Java Web Service that accepts Web Service requests to a defined API, turns around and passes them on to Microsoft Exchange. 

In the diagram at left, therefore, the EWSAPI project contains the code that is the endpoint for the arrow numbered 1.1, and the source for the arrow numbered 1.2.

An important part of a SOA architecture is a canonical data model that can be used to abstract the integration from the implementation, and for me, this service will later act as the separation point between the business layer and the Microsoft Exchange communication. As I work through this in more detail, it is becoming apparent that what I am currently thinking of as the EWSAPI will ultimately be split into two completely separate components. I think there is a good case to be made for an abstraction API that allows you to plug in other adapters to work for other calendar and e-mail servers.

The source code for this article in the EWSAPI project contains three WebService operations:

Something to be aware of is that the APIs that we are going to be working with can do substantially more than I am going to talk about in this article. For example, an appointment can have an invitee list, recurrence, attachments, and so on. I am more focused on showing you how to create the appointment on the Exchange Server, retrieve the appointment from Exchange, and delete it. I may come back to the more detailed stuff in later posts.

As an aside, I wrote most of this code before I decided on the structure of these articles. My test environment is already set up to support things like Exchange Impersonation and Subscriptions. As a result, some of the code that I am providing you here already supports some of that functionality, but some of it is only half-implemented, so don't go onto those things until we get to those articles because the code may not work properly. 

OEEWS – OpenEdge Exchange Web Services API

Appointment Viewer - PopulatedThe second project is the OEEWS project which contains the OpenEdge ABL code that allows you to call each of these APIs from OpenEdge. I have created a .NET style UI in OpenEdge that allows you to connect to the service, generate 10 appointments automatically and randomly, fetch an appointment from the server, view it, and delete it.

Yes, I know my UI is ugly, but it's a prototype so who cares. If you don't like it, fix it.

The OEEWS component corresponds with the trigger point for the 1.1 arrow in the diagram above, so this article is purely about communicating with Exchange. We are not worried about the subscription API (all the #2 arrows) just yet. 

Another thing to know about the OEEWS code is that I have liberally mixed procedural, object-oriented and .NET UI code in together. My primary audience for the OpenEdge code is people who have never worked with the OO stuff before, so I want them to get comfortable with it and realize that it is not that complicated.

Now that you know what is in the mix, let's get down to the code and understand what is going on. If you are an OpenEdge ABL developer, this first section is going to look a little scary – it's Enterprise Java. You may not recognize the code, but I am pretty sure you will understand it once I have walked through it, so hang on tight. You won't want to skip down to the ABL section because there are concepts that I am going to talk about that you need to understand in the following section.

The EWS API

The Java-based EWSAPI consists of 5 source code files in 3 packages. The files are:

  • EWSCalendar.java – Contains the source code file for the web service itself and it lives in the org.tsg.ews package;
  • Appointment.java – Contains a class that represents a Calendar item, including properties for the appointment and methods to create, get, and delete appointments. It lives in the org.tsg.ews.calendar package;
  • EWSResponse.java – Contains the source code for a class that acts as a container for the response that is returned from Exchange. It lives in the org.tsg.ews.service package;
  • ExchangeService.java – Contains a class that abstracts the connection to an Exchange Web Service. It lives in org.tsg.ews.service package; and
  • SOAPRequest.java – Contains a class that is responsible for actually calling the Exchange Web Service.

Go ahead and open Eclipse for Java EE, and, assuming you have already imported the EWSAPI project into your workspace, expand the EWSAPI node. Underneath that, expand the "Java Resources: src" node and expand each of the packages. Then go ahead and open EWSCalendar.java so that your UI looks like this:

Eclipse for Java EE showing EWSCalendar

Now let's dig into this code. 

EWSCalendar

The EWSCalendar class is the main entry point in the the Java code on the server. The class exposes a Web Service that consists of 3 methods:

  • createAppointment;
  • getAppointment; and
  • deleteAppointment.

Each of these methods does pretty much what its name implies. The code in EWSCalendar actually does very little work. It is the facade that exposes the service for use as a Web Service. 

Web Services Annotations

Setting up a Web Service in Java EE is as simple as annotating a class for use as a Web Service. In line 15, there is an attribute that annotates the following class, EWSCalendar, as a Web Service with a name "AppointmentService". 

Lines 18 and 19 contain annotations that specify that createAppointment should be treated as a Web Service operation called "CreateAppointment" and that the node in the SOAP document that is returned from the call should have a node called "Response" that contains the output parameter for the call. In line 20, createAppointment is defined as returning an EWSResponse object, and the remainder of line 20 through line 30 define the parameters for the createAppointment call. Each parameter is annotated with a WebParam annotation that provides the name of the XML node that must be created for its value.

Lines 45 through 51 do the same thing for the GetAppointment operation and lines 58 through 64 do the same thing for DeleteAppointment. That's all that is required to define a Web Service. I don't need to generate a WSDL or anything. Java EE does that all for me automatically based on the annotations for the methods.

Now if you are a Java programmer looking at the parameters for this call, you may be wondering why the parameters are not defined as objects. The simple answer is because it is a lot easier to work with this call in OpenEdge if each parameter is specifically defined, than it is to expect an appointment object, say, as an input parameter for CreateAppointment.

CreateAppointment

Going back to to line 31, the code for CreateAppointment looks like this:

ExchangeService srvc = new ExchangeService(hostName, domain, username, passwd);
Appointment appt = new Appointment(srvc);
appt.setDescription(description);
appt.setLocation(location);
appt.setStartDate(startDate);
appt.setEndDate(endDate);
appt.setNotes(notes);
appt.setReminder(reminder);
appt.setReminderMinutes(minutes);
EWSResponse resp = appt.create(); 
return resp;

In line 32, I create an ExchangeService object, using the host name for the Exchange CAS Server that hosts the Exchange Web Service API, the domain name of the user for whom an appointment is being created, and the user name and password for that user. We'll dive into what this code actually does in a few minutes, but for the moment let's finish off the createAppointment call.

Line 33 instantiates an Appointment object using the ExchangeService object created in the line before and lines 33 through 39 set a number of properties of that appointment. Description is actually the subject of the appointment and lines 38 and 39 set the reminder for the appointment. 

Line 40 calls the create method on the appointment and this is the code that does all the work. The create method returns an EWSResponse object, which is annotated for JAXB XML serialization to produce the XML results that we need.

The Appointment Object and JAXB XML Serialization

Now I'd like you to open Appointment.java in the org.tsg.ews.calendar package. You can skip down to lines 22 and 23, because there is something here that affects getAppointment later. You'll note in these two lines that there are two more annotations:

@XmlRootElement(name = "Appointment")
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Appointment {

The two @Xml annotations are for JAXB which will automatically serialize the whole Appointment class into a root element called "Appointment." The second annotation tells JAXB to look at all get… and set… methods as properties and turn them into XML. If you scroll down to line 70, you'll see an annotation "@XmlElement(name = "Subject") just before the getDescription method that tells JAXB to serialize this property into an XML element (not an attribute) called "Subject". So the value of this property will be serialized as follows:

<Subject>Go catch some flies - 01</Subject>

Again, XML serialization for classes is built in to Java EE. This piece of things is important for you to understand, because the same mechanism has been applied to EWSResponse so that its data can be serialized into the SOAP message that will be the return value to the CreateAppointment call.

The Create Method

Now scroll down to the create() method in Appointment.java in line 278:

public EWSResponse create() {
	DatatypeFactory dtf = DatatypeFactory.newInstance();
	StringBuffer request = new StringBuffer();
	request.append("<CreateItem \n");
	request.append("               SendMeetingInvitations=\"SendToNone\"\n");
	request.append(String.format("               xmlns=\"%1$s\"\n", SOAPRequest.MESSAGES_URI));
	request.append(String.format("               xmlns:t=\"%1$s\">\n", SOAPRequest.TYPES_URI));
	request.append("    <Items>\n");
	request.append("        <t:CalendarItem>\n");
	request.append("            <t:Subject>");
	request.append(description);
	request.append("</t:Subject>\n");
	if (notes != null) {
		request.append("            <t:Body BodyType=\"Text\">");
		request.append(notes);
		request.append("</t:Body>\n");
	}
	if (reminder) {
		request.append("            <t:ReminderIsSet>true</t:ReminderIsSet>");
		request.append("            <t:ReminderMinutesBeforeStart>");
		request.append(Integer.toString(reminderMinutes));
		request.append("</t:ReminderMinutesBeforeStart>\n");
	}
	if (startDate != null) {
		request.append("            <t:Start>");
		request.append(dtf.newXMLGregorianCalendar(startDate).toXMLFormat());
		request.append("</t:Start>\n");
	}
	if (endDate != null) {
		request.append("            <t:End>");
		request.append(dtf.newXMLGregorianCalendar(endDate).toXMLFormat());
		request.append("</t:End>\n");
	}
	if (location != null) {
		request.append("            <t:Location>");
		request.append(location);
		request.append("</t:Location>\n");
	}
	request.append("        </t:CalendarItem>\n");
	request.append("    </Items>\n");
	request.append("</CreateItem>\n");

Lines 280 through 319 simply build up the body of the SOAP message that needs to be sent to Exchange for it to create the appointment.

One of the things about Exchange Web Services that is critical to understand is that there can be no mistake in this XML. It needs to appear exactly as it is defined in the Exchange SDK, otherwise it will not work. The order of the elements is absolutely critical. I have spent a lot of time trying to figure out why a call was not working, only to find that the the XML was badly formatted or a node was spelled incorrectly. Exchange does not help you here. You simply get a 500 – Internal Server Error message from the Web Server which is about as much use as an udder on a bull.  

In line 282, we start out by defining a CreateItem node that contains an Items node that has one CalendarItem node in it. Namespacing is very important, so lines 284 and 285 set up the appropriate namespaces from constants defined in the SOAPRequest class.

Lines 288 through 290 serialize the subject while lines 291 through 295 handle the Notes or Body of the appointment. Lines 296 through 301 handle the reminder and lines 302 through 311 handle the start and end dates for the appointment. After handling the location in lines 312 through 316, we end off by closing out the XML tags.

The rest of the code in this method looks like this:

SOAPRequest req = new SOAPRequest(request.toString(), emailAddress);
EWSResponse retVal = service.makeRequest(req);
lastResponse = retVal;
id = retVal.getId();
changeKey = retVal.getChangeKey();
return retVal;

Now it's important to realize that all we have created is the part of the SOAP message that tells Exchange what we want it to do – create a calendar item. We haven't wrapped it in a SOAP message yet. Line 321 takes care of doing this by creating a SOAPRequest object based on the request string that we just created. Ignore the fact that we pass in an emailAddress at this point, because that will be used later for the Exchange Impersonation article.

If you want to go and explore the SOAPRequest class, feel free to do so. It simply creates the SOAP envelope and plugs the request information that we passed in into the body of the SOAP message. No rocket science in this.

The next step (line 322) is to call the makeRequest() method on the ExchangeService object that was passed in when we created the appointment.  We'll dig into this call in a minute, but let's just finish out understanding what the rest of this method is doing. 

The makeRequest() method call returns us an EWSResponse object that we can return to the caller. Before we do that, though, we set two very important pieces of information inside this Appointment object.

Items and Item IDs

When any item (e-mail, contact, task, or appointment) is created on an Exchange Server, it is allocated two pieces of information:

  1. An Item ID. This is universally unique inside Exchange. No two items will ever have the same Item ID, even across different mailboxes, and this becomes the mechanism that we need to refer to the item later;
  2. A ChangeKey. This piece of information is almost like a SHA1 or MD5 checksum that tells what the state was of the item at the point that you created it. This becomes very important later when we update these items.

You'll note that I have switched from referring to the things we are creating as Calendar items to simply calling them Items. That's because in Exchange, everything that can be created inside a folder (something we'll deal with later) is considered an Item. E-mail, Contacts, Tasks and Appointments are all items. This is another important concept to get clear, because if you look back at line 282, you'll note that the node that we created was a CreateItem node and that it contained a CalendarItem node inside an Items node.

Lines 324 and 325 set the values of the Item ID and ChangeKey for this appointment respectively. I also store the EWSResponse object in the Appointment in case it is needed later. I don't have code that uses this right now, though. Line 326 returns the EWSResponse object to the caller and lines 328 through 330 catch any exceptions and turn them into an EWSResponse object that can be serialized.

ExchangeService

The next thing to do is to take a look at the code in the ExchangeService class in the org,tsg.ews.service package. The guts of the communication with Exchange happens in the makeRequest method, which begins at line 57. You'll note that the first thing that makeRequest does is call "setAuthenticator" in line 58. The code in setAuthenticator is critical to our communication with Exchange, so scroll down to line 91 and we will pick up there.

setAuthenticator

The following is the code that is contained in setAuthenticator:

private void setAuthenticator() {
	System.setProperty("http.auth.preference", BASIC_AUTHENTICATION);
    	System.setProperty("http.auth.digest.validateServer", "false");
    	System.setProperty("http.auth.digest.validateProxy", "false");
		
	Authenticator.setDefault(new Authenticator() {
		@Override
		protected PasswordAuthentication getPasswordAuthentication() {
			StringBuffer user = new StringBuffer(domain);
			user.append("\\");
			user.append(username);
			System.out.println("Authentication...");
	   		System.out.print("Requesting Prompt: ");
			System.out.println(this.getRequestingPrompt());
	    		System.out.print("Requesting Scheme: ");
			System.out.println(this.getRequestingScheme());
	   		System.out.print("Requestor Type: ");
			System.out.println(this.getRequestorType());
			
			return new PasswordAuthentication(user.toString(), password.toCharArray());
		}
			
	});
}

This code has gone through a number of iterations and there are a lot of debug messages that I have put in here to try and figure out what I could do about the authentication piece of things. As I mentioned in part 1, this code currently only works with basic authentication switched on. The first System.setProperty call sets the authentication mode to basic authentication. The next two properties only apply with digest authentication and you can ignore them. 

The Authenticator.setDefault() call sets a default authenticator class that takes the user name and password that were supplied earlier, and makes them available for authentication purposes when the Exchange Web Service requests credentials.

The big problem with this code is that it sets these credentials for the entire JVM. This is a bad thing, especially in the Java EE environment, but there are solutions that I have already found to fix this problem that I will blog about later. For now, the purposes of what I am currently trying to achieve, this is good enough, and an important thing to remember is that I am looking at this as server-side code so Exchange Impersonation is a key component of the strategy going forward. In the impersonation model, the problem is not as serious as it is right now.

The reason this is such a serious risk right now is that you need to shut down and restart the Glassfish AppServer between changing the user for whom the appointment is being created. But I figured that as this is the only article that will use this model, we're OK with that limitation.

Making the call

Now we need to go back to line 60 in the makeRequest function:

URL ewsURL = new URL(String.format(EWS_URL, this.hostName));  // Substitute the host name into the URL.);
HttpsURLConnection ewsConn = (HttpsURLConnection) ewsURL.openConnection();
ewsConn.setRequestMethod("POST");
ewsConn.setRequestProperty("Content-type", "text/xml;utf-8");
ewsConn.setDoInput(true);
ewsConn.setDoOutput(true);

Line 60 sets up a URL for the Exchange Server. The String.format call substitutes the hostName provided into the EWS_URL constant so that the Exchange Web Service API is called. The constant is defined in line 25.


Update: Since I wrote this article, I have installed and tested Microsoft Exchange Server 2007 SP3 and Microsoft Exchange Server 2010. There is one minor modification you will need to make to the sourced code that accompanies this article for it to work with these. Line 63 of the source code file "ExchangeService.java" that accompanies this article reads:

ewsConn.setRequestProperty("Content-type", "text/xml;utf-8");

It may need to be changed to read:

ewsConn.setRequestProperty("Content-type", "text/xml;charset=utf-8");

Next we set up an https connection, set the request method to post, and several other properties of the connection. Line 67 contains a debug message that displays the entire XML for the SOAP message just prior to posting the call to the Exchange Web Server and lines 69 through 72 actually post the message to the Exchange Server:

PrintWriter pout = new PrintWriter(new OutputStreamWriter(ewsConn.getOutputStream(), "UTF-8"), true);
pout.print(message.getMessage());
pout.flush();
pout.close();

Receiving the response

All calls to Exchange are followed by a response:

EWSResponse resp;
if (ewsConn.getResponseCode() == HttpsURLConnection.HTTP_OK) {
        BufferedReader bin = new BufferedReader(new InputStreamReader(ewsConn.getInputStream()));

        StringBuilder result = new StringBuilder();
        String line;
        while( (line = bin.readLine()) != null )
        	result.append(line);
        System.out.println(result.toString());
        resp = new EWSResponse(result.toString());

In line 75, we check to see that the response we got from the Web Service was an OK – 200. If it is, line 76 instantiates a BufferReader to read the URL connection's input stream. Lines 78 through 81 read the contents of that stream into a string which we display in line 82 so we can see the response we got from Exchange.

Line 83 then takes that response and instantiates an EWSResponse object with the string we got back, and the EWSResponse object parses the SOAP message that we received from Exchange and stores the response in object. Among the things that is returned is a ChangeKey, an ID and the error status of the call. I'm not going to spend much time talking about EWSResponse here, other than to say that it parses the XML using the standard DOM in the JVM.

The remainder of the makeRequest() method deals with what happens if we don't get an OK http response:

} else {
	System.out.println (String.format("Error code: %1$s - %2$s", ewsConn.getResponseCode(), ewsConn.getResponseMessage()));
	resp = new EWSResponse("HTTPError", ewsConn.getResponseCode(), ewsConn.getResponseMessage());
}
return resp;

Line 85 provides the details of the response as a debug message and line 86 creates an EWSResponse object that provides details of the error. The most likely scenarios for this response are 401 errors (HTTP authentication failed) or 500 errors (the XML was not properly understood by Exchange and it reports an "Internal Server Error"). The 500 errors are a pain because there is no information available on why you got them. You have to guess, and it is this that made me hand-code the XML that is being sent to the Exchange Server.

Now that we have an EWSResponse object, it can be returned to the caller, serialized and returned from the WebService to whatever the source was.

Testing it out with soapUI

The next step is to try and test this. Make sure you have your Glassfish server running and that the EWSAPI code is deployed to the server. You can check this by switching to the Servers view in Eclipse for Java EE and looking at the status of the GlassFish V3 Java EE 6 server. Start the server if necessary and drag the whole EWSAPI node to the GlassFish server node and it will be deployed for you.

The next step is to start soapUI and create a new project. Right-click on the Projects node and create a new project. You should see the following dialog:

Creating the soapUI project for AppointmentService

The URL that need to type in the "Initial WSDL/WADL" field is:

http://localhost:8080/EWSAPI/AppointmentService?wsdl

Once you have entered that URL, make sure the check boxes are as they appear in the screenshot above, and choose the OK button. SoapUI will generate a project for AppointmentService and it will have three operations under it. Go ahead and expand the tree so that all the nodes are visible. The three operations are CreateAppointment, GetAppointment, and DeleteAppointment. Each of them has a "Request 1" node under it. I have renamed those nodes to "CreateApptRequest", etc, so that when a request is open, I can differentiate it from the others.

Before we execute a call, open Outlook for the account that you are testing against so you can see what happens in Outlook when the appointment is created.

Soap UI calling CreateRequest

Double-click on the CreateAppointment request in soapUI and open the XML editor will open with the XML Soap message in the left-hand pane. Remember, there is no validation, so make sure you type all the right values between the tags in the XML document.

The first parameter for the call is the Exchange Server. You need to type the fully qualified domain name of the Exchange Web Services host that you are trying to call. Next is the domain. This is the domain that is used for the Windows login. Username and password are fairly self-explanatory, as are the Description, Location and Notes elements. 

SetReminder should be either "true" or "false". If "true", a reminder will be set for the appointment that will go off a certain number of minutes before the appointment. ReminderMinutes is an integer that specifies how many minutes lead-time the reminder should have.

StartDateTime and EndDateTime are exactly what they say they are, but they are ISO dates that include a time zone. The date is in yyyy-mm-dd order and is followed by a T to indicate that the rest of the field is all time. The time is a 24 hour time format, including a 3 digit decimal for the number of milliseconds. So 5:00pm is 17:00:00.000. The last section of the time is an offset from Universal Standard Time(UTC). I'm on the West Coast of the US, so in the Northern Hemisphere Summer, we are 7 hours behind UTC. Thus my timezone information is -07:00. If I were in South Africa, I'd be two hours ahead of UTC which would be +02:00.

Once you have all these fields filled out, you can execute the call. In the right-hand pane of the request, you should get a SOAP message back that tells you what happened. If all went well, there will be a single response node that contains three attributes – ChangeKey, ItemID and ErrorStatus. The ErrorStatus should be "NoError" and the other two attributes will have very long strings in them that mean nothing, but I've already explained what these are. 

If you get this, your appointment has been created in Exchange and if you have Outlook open in the background, a second or two later the appointment will show up in the calendar as you can see in the screenshot above.

If you are having trouble getting any kind of response back, go to Eclipse for Java EE, and switch to the Glassfish Server log in the Console view as follows:

Glassfish Server Log View

You will need to select the down-arrow next to the console button in the toolbar in the Console view. Select the log file from the two console sources, and it will show the messages that were output as the code was running.

Now that we have successfully created the appointment, you need to keep the result of the call around, because you are going to need it in a few minutes when we run the GetAppointment call.

For the moment, though, let's switch back to the Eclipse for Java EE and look at the code for GetAppointment and DeleteAppointment.

GetAppointment

If we switch back to the EWSCalendar class, lines 45 through 56 contain the code for retrieving an appointment from Exchange:

@WebMethod(operationName = "GetAppointment")
@WebResult(name = "Appointment")
public Appointment getAppointment(@WebParam(name = "ExchangeServer") String hostName,
				@WebParam(name = "Domain") String domain,
				@WebParam(name = "User") String username,
				@WebParam(name = "Password") String passwd,
				@WebParam(name = "AppointmentID") String id) {
	ExchangeService srvc = new ExchangeService(hostName, domain, username, passwd);
	Appointment appt = new Appointment(srvc);
	appt.retrieve(id);
	return appt;
}

As I mentioned earlier, the first couple of lines are annotations needed for exposing this call as a Web Service called GetAppointment. Note that this method returns an object of type Appointment, the class we learned about earlier on. All this call does is instantiate an ExchangeService object, instantiate an Appointment object, retrieve it, and return it. The Java EE WebServices framework takes care of serializing the Appointment object to XML.

The retrieve() method takes an Item ID as a parameter so that it retrieves the calendar item by means of that Item ID.

Appointment.retrieve()

The retrieve() method in line 157 of the Appointment class is responsible for crafting the XML message that gets sent to the Exchange Server:

public EWSResponse retrieve(String itemID) {
	try {
		StringBuffer request = new StringBuffer();
	        request.append("<GetItem \n");
	        request.append(String.format("               xmlns=\"%1$s\"\n", SOAPRequest.MESSAGES_URI));
	        request.append(String.format("               xmlns:t=\"%1$s\">\n", SOAPRequest.TYPES_URI));
	        request.append("    <ItemShape>\n");
	        request.append("          <t:BaseShape>Default</t:BaseShape>\n");
	        request.append("          <t:AdditionalProperties>\n");
	        request.append("              <t:FieldURI FieldURI=\"item:Body\"/>\n");
	        request.append("              <t:FieldURI FieldURI=\"item:ReminderIsSet\"/>\n");
	        request.append("              <t:FieldURI FieldURI=\"item:ReminderMinutesBeforeStart\"/>\n");
	        request.append("          </t:AdditionalProperties>\n");
	        request.append("    </ItemShape>\n");
	        request.append("    <ItemIds>\n");
	        request.append(String.format("        <t:ItemId Id=\"%1$s\"/>\n",itemID));
	        request.append("    </ItemIds>\n");
	        request.append("</GetItem>\n");
		SOAPRequest req = new SOAPRequest(request.toString(), emailAddress);
		EWSResponse retVal = service.makeRequest(req);
		if ((!retVal.hasError()) && retVal.getElement() != null) {
			parseItem(retVal.getElement());
		}
		lastResponse = retVal;
		return retVal;
	}
	catch (Exception e) {
		return new EWSResponse(e);
	}
}

The first thing to note about this code is that there is nothing in it that indicates that the object that we are retrieving is a Calender Item. As far as Exchange is concerned, we are retrieving an item, and it does not care what type of item it is. So the call we make is a GetItem call.

Lines 163 through 170 define an ItemShape object. The ItemShape tells Exchange what we are interested in retrieving about this item. The BaseShape node tells it to retrieve the most basic properties of the item, but we need additional data like the Body (or notes), the Reminder flag and the number of minutes in advance that the reminder is required. So the AdditionalProperties node asks Exchange to retrieve these properties, too.

Lines 171 through 173 tell Exchange what item IDs to return – in our case there is always only one.

In line 175 we create the SOAP envelope for the call and in line 176 we call makeRequest to retrieve the data from Exchange. Assuming all went well with the call, we set the values of all the fields in the Appointment by calling parseItem() in line 178. I'm not going to spend a lot of time explaining the XML parsing. Just take it from me that the code parses the XML node for the appointment and gets the right values out.

Of course, if the call failed, we need the client to know that, so we store the EWSResponse object in the appointment. The advantage of that is that the EWSResponse object is XML-serializable so it will be serialized with the Appointment object. Another nice thing about JAXB is that it will ignore null elements (unless you tell it not to). What this means is that if the Appointment object's properties are null, they will not be written to the XML response.

That's pretty much it. Now let's see what happens when we run it.

Testing GetAppointment with soapUI

Before you switch back to soapUI, go to the appointment that we created earlier, and make some changes to it in Outlook. Add some stuff in the notes and change the date and time or something. Now switch back to soapUI and highlight and copy (Ctrl+C) the Item ID that is in the Response node in the right side pane of the CreateApptRequest window we opened earlier

Now open the GetAppointment request and set all the values for the credentials as you did earlier:

soapUI showing GetAppointment

The Appointment ID node needs to contain the Item ID that you copied from the CreateApptRequest above. Once you have pasted that in and executed the call, you should get a response that contains all the information about the appointment that we requested. Note that the changes that you made are retrieved with the call. I should mention that sometimes you have to wait a few seconds for Exchange to commit your changes and notify the Client Access Server before your changes become available for retrieval by Exchange Web Services.

DeleteAppointment

Last, but not least is DeleteAppointment. The code for this is actually very simple. If we go back to Eclipse for Java EE and look at EWSCalendar again, lines 58 through 67 contain the code that does the work:

@WebMethod(operationName = "DeleteAppointment")
@WebResult(name = "Response")
public EWSResponse deleteAppointment(@WebParam(name = "ExchangeServer") String hostName,
				@WebParam(name = "Domain") String domain,
				@WebParam(name = "User") String username,
				@WebParam(name = "Password") String passwd,
				@WebParam(name = "AppointmentID") String id) {
	ExchangeService srvc = new ExchangeService(hostName, domain, username, passwd);
	EWSResponse resp = Appointment.delete(srvc, id, null);
	return resp;
}

By now, hopefully you understand everything down to line 66. In line 66, we call Appointment.delete() and pass in the service and an Item ID. When the call is complete, we simply return the EWSResponse object that should give us error status.

Appointment.delete()

The guts of the work happens in the Appointment object, so switch to that editor and go to line 26:

public static EWSResponse delete(ExchangeService srvc, String itemID, String emailAddress) {
	try {
		StringBuffer request = new StringBuffer();
	        request.append("<DeleteItem \n");
	        request.append(String.format("               xmlns=\"%1$s\"\n", SOAPRequest.MESSAGES_URI));
	        request.append(String.format("               xmlns:t=\"%1$s\"\n", SOAPRequest.TYPES_URI));
	        request.append("           DeleteType=\"MoveToDeletedItems\"\n");
	        request.append("           SendMeetingCancellations=\"SendToNone\">\n");
	        request.append("    <ItemIds>\n");
	        request.append(String.format("        <t:ItemId Id=\"%1$s\"/>\n",itemID));
	        request.append("    </ItemIds>\n");
	        request.append("</DeleteItem>\n");
		SOAPRequest req = new SOAPRequest(request.toString(), emailAddress);
		EWSResponse retVal = srvc.makeRequest(req);
		return retVal;
	}
	catch (Exception e) {
		return new EWSResponse(e);
	}
}

Again, hopefully this code looks very similar to the GetItem call we did earlier. The DeleteItem node has two interesting attributes in lines 32 and 33. You'll see the DeleteType attribute which tells Exchange to move the deleted appointment to the Deleted Items folder. You can specify exactly what to do with the item, including a hard delete – permanent deletion.

In line 33, the SendMeetingCancellations attribute tells Exchange what to do about notifying people about the cancellation of the appointment.

Line 34 through 36 contains the list of Item IDs to be deleted (in our case, just one). Lines 38 through 40 create the SOAP envelope, make the call to Exchange and obtain and return the EWSResponse object. That's all there is to it.

Testing DeleteAppointment with soapUI

The last step in the Java tests is to see if the DeleteAppointment API works. Make sure you have the Item ID for the appointment you created earlier, and open the DeleteAppointment request. As with the GetAppointment API, paste the Item ID into the Appointment ID node:

Delete Appointment Request in soapUI

If all goes well, the response will be contained in a Response node and the ErrorStatus will be NoError. If you switch to Outlook, the appointment has been removed and if you switch to the Deleted Items folder, you will see the Calendar Item in that folder.

Outlook Deleted Items

Java Summary

That's all there is to the Java side of things for this article. You have now learned how to use Java to create, get and delete calendar items using the Exchange Web Services API. The next article will focus on Exchange Impersonation.

If you're a Java developer, and you have no interest in the OpenEdge component, thank you for your attention. I hope this was useful to you.

If you are an OpenEdge developer, don't go anywhere – we're going to see how to call this Web Service from inside OpenEdge. 

The OE EWS Components

If you have been following the code up until now, you have a running instance of Glassfish and it's ready for you to test the OpenEdge code that calls these three Web Service operations. Go ahead and open OpenEdge Architect and make sure you have imported the OEEWS project into OpenEdge Architect.

Once you have imported it, expand the src folder all the way down until your Resources are expanded as in the screenshot below:

OEArchitect1

Just so there is no confusion, my screenshots were taken of the code as I have it checked into my Subversion repository, so you can ignore the date, time and user name next to each source code element. That is information that Subversion adds.

The first code that we are going to look at is in the CreateAppointments.p procedure as this code creates a batch of 10 appointments, so go ahead and open this procedure in OE Architect.

CreateAppointments.p

There really is no rocket science about this code. For the most part, it is ordinary OpenEdge ABL/4GL code. Lines 14 and 15 of the source file do contain two statements that are specific to object-oriented ABL:

USING Progress.Lang.*.
USING com.tsg.ews.*.

These two lines tell the compiler that my code references objects in the Progress.Lang package and in the com.tsg.ews package. Don't fret about this just yet.

Line 20 references a temp-table definition that is in an include file while lines 21 through 24 define 3 input parameters and an output parameter table for the temp-table defined in the include file.

The rest of the code down to line 39 is pretty standard 4GL, but in line 39, I define a variable called "appt" that is of type com.tsg.ews.Appointment. If you cast your eyes across to the Resources view, you will see a file called Appointment.cls under the src/com/tsg/ews folder. Appointment.cls contains an OpenEdge class definition, and we will look at this code a little later. The Using statement in line 15 told Progress to look in the com/tsg/ews folder for any classes that I refer to.

The interesting code starts in line 43 (I have removed comments below for brevity):

REPEAT iCount = 1 TO 10:
    
    appt = new Appointment().
    appt:Subject = pcDescription + " - " + STRING(iCount, "99").  
    appt:Location = pcLocation.
    appt:Notes = pcNotes.
    appt:StartDateTime = figureOutStartDate(dWorkDate).     
    appt:EndDateTime = DATETIME-TZ(DATE(appt:StartDateTime), MTIME(appt:StartDateTime) + iDuration, TIMEZONE ).
    dWorkDate = appt:EndDateTime.
    
    appt:SaveAppointment().        
    
    CREATE ttAppointment.
    IF appt:ItemID <> ? AND
       appt:ItemID <> "" THEN
       cString = appt:ItemID.
    ELSE
       cString = STRING(iCount).  
        
    ASSIGN
        ttAppointment.description = appt:Subject
        ttAppointment.startdate = DATETIME(DATE(appt:StartDateTime), MTIME(appt:StartDateTime)).
        ttAppointment.enddate = DATETIME(DATE(appt:EndDateTime), MTIME(appt:EndDateTime)).
        ttAppointment.itemid = cString.
    .
    
END.

This repeat loop occurs 10 times. In line 46, I create a new instance of an Appointment object and I assign the reference to the appt variable. What this means is that I have an empty Appointment object. In lines 21- 23 I defined 3 input parameters for the appointment description, location and notes. In 47, 48 and 49, I set the Appointment's corresponding properties from these input parameters.

In line 50, I set the start date and time for the appointment to the return value of the figureOutStartDate function. I'm not going to walk through the code for the function here, but I'll tell you that it goes and calculates a date on a work day (Monday through Friday) between 9 and 5, skipping an hour for lunch at 12:00pm. It also sets a variable that is global to this procedure called iDuration. This contains the length of the appointment in milliseconds. In line 51, I set the end date and time to the start date and time plus the duration.

The upshot is that I will get 10 appointments that follow each other of random duration and with random gaps between them starting from now, until all 10 appointments are scheduled.

Line 57 calls SaveAppointment and this method on the Appointment object is responsible for calling the Web Service. We'll get to this code in a minute or two. When the appointment is successfully saved, we set the ItemID property of the Appointment object to the return value from Exchange.

Lines 60 through 72 create a temp-table record for this appointment and store the subject, start date, end date and item id. This is just regular 4GL code. 

Appointment.cls

 

Next, we'll take a look at the code in Appointment.cls. Go ahead an open it in the editor.

Appointment.cls is an OpenEdge ABL class that uses OpenEdge Object-Orientation. Lines 16 through 20 declare the class and define some variables. Lines 22 through 24 define the default constructor for the Appointment object. This is the code that is called when an object is instantiated as it was in line 46 of CreateAppointment.p. Line 26 through 30 declares a constructor that we will use later for GetAppointment. 

Lines 32 through 68 declare several properties for the Appointment object that we used to set the appointment values in lines 47 through 51 of CreateAppointments.p above.

Skip on down to line 87, and we will take a look at the code that actually saves the appointment:

METHOD PUBLIC LOGICAL SaveAppointment():
    DEFINE VARIABLE srvc AS EWSService NO-UNDO.
    DEFINE VARIABLE hHandle AS HANDLE NO-UNDO.
    DEFINE VARIABLE cRetVal AS LONGCHAR NO-UNDO.
    DEFINE VARIABLE cNotes AS LONGCHAR NO-UNDO.
    cNotes = "<![CDATA[" + THIS-OBJECT:Notes + "]]>".
    srvc = EWSService:GetService().
    hHandle = srvc:CalenderAPI.
    RUN CreateAppointment IN hHandle 
       (INPUT srvc:ExchangeServer,
        INPUT srvc:Domain,
        INPUT srvc:UserName,
        INPUT srvc:Password,  
        INPUT THIS-OBJECT:Subject,
        INPUT THIS-OBJECT:Location,
        INPUT THIS-OBJECT:StartDateTime,
        INPUT THIS-OBJECT:EndDateTime,
        INPUT cNotes,
        INPUT THIS-OBJECT:ShouldSetReminder,
        INPUT THIS-OBJECT:ReminderMinutes,
        OUTPUT cRetVal). 
    parseResponse(cRetVal).
    RETURN TRUE.
END.

The first few lines (88-91) define variables that we will use later. In line 92, we wrap the Notes field in a CDATA node so that we can handle any data that can be put in it. It could contain HTML, XML or any other kind of data. 

Line 93 calls the GetService method on the EWSService object. EWSService is another class that I created and all it does is wrap the processing around the connection information for the Java and Exchange Web Services. It also takes care of establishing the connection to the Java Web Service. In line 94, we get the handle to that Web Service connection and in lines 96 through 107, we make the call to the Web Service and receive the response in the cRetVal variable.

Line 108 takes care of parsing the response so that the ItemID is stored into the appointment object.

That is all there is to it. Now let's take a look at how we call this code. 

AppointmentViewer.cls

Go to the Resources view and right-click on the AppointmentViewer.cls file. From the context menu, select "Open with…->OpenEdge Visual Designer." Your screen should look like this:

OpenEdge Appointment Viewer

The AppointmentViewer class is a .NET style UI with OpenEdge code behind it. Double-click on the "Generate Appointments" button and you will be taken to a section of code in this form that calls the CreateAppointments.p procedure we looked at earlier. The code is in line 81 through 92 of the editor that is now open:

METHOD PRIVATE VOID btnGenerate_Click( INPUT sender AS System.Object, INPUT e AS System.EventArgs ):
    CLOSE QUERY qAppointment.
    RUN CreateAppointments.p (INPUT txtSubject:Text, INPUT txtLocation:Text, INPUT txtNotes:Text, OUTPUT TABLE ttAppointment).
    OPEN QUERY qAppointment
        FOR EACH ttAppointment NO-LOCK
            BY ttAppointment.startdate.
    THIS-OBJECT:bindingSource1:Handle = QUERY qAppointment:HANDLE.
    bindingSource1:RefreshAll().
    dataGridView1:Refresh().
    RETURN.
END METHOD.

This code closes the qAppointment quert, which is a query based on the ttAppointment temp-table defined in the ttappointment.i include file. The code then calls CreateAppointments.p which returns the ttAppointment table. The input parameters for this call are derived from the Subject, Location and Notes text boxes that are at the top of the Appointments tab in the AppointmentViewer window. Once the call is complete, we reopen the query, refresh the binding source and data grid so that the data is displayed on the screen.

Running the Appointment Viewer

Empty appointment viewerNow it's time to run the Appointment Viewer. From the OpenEdge Architect Run menu, choose Run->Run as…->OpenEdge Application. If you are asked to save the file, you can go ahead and do so. When you run the code, you should see the screen at left.

Enter the name of your Java Server and make sure you include the port number, which, by default, will be 8080. For these initial runs, you can make the call using localhost for the name of the server.

The other prompts in the top half of the screen are the same as those we referred to in the calls that we made from soapUI above.

Before you generate the appointments, you should make sure that Outlook is open in the background so you can see the effect of the call.

Once you enter the password, the Generate Appointments button is enabled. Go ahead and enter a Subject, Location and some notes and then choose the Generate Appointments button.

If things are working properly, you will see a slight pause and the grid at the bottom of the screen will populate with a list of appointments that were automatically generated for you by the CreateAppointments.p procedure. 

Your populated screen should look something like the following screen. It is possible that you will see a series of numbers in the Item ID column. If you do, there was a problem creating the appointments on the server. If the appointments were created properly, you will see the ItemID populated with very long strings of characters as below:

Populated appointment viewer

In Outlook, you should now see a set of 10 appointments that were created that correspond with the dates and times in the data grid in the Appointment Viewer. Select a row by clicking on the gray area to the left of the data grid. When you have selected a row, the entire row will be highlighted an the Get Appointment button will become active. Choose the Get Appointment button and your screen should look like this:

Viewing appointments 1

You will note that the data in the AppointmentView dialog that pops up includes data that we do not have in the temp-table. That's because we fetched the appointment from the Exchange Server, not from the local cache. If you compare the appointment with the appointment properties for the corresponding appointment in Outlook, you will see that they match, so go ahead and edit the contents of the appointment in Outlook – something like this:

Outlook vs Appointment View

Now save and close the appointment in Outlook and give yourself a good 10 to 15 seconds so that Exchange has a chance to let the Client Access Server know about the change, then close the AppointmentView dialog by choosing the OK button, and open it again by choosing the Get Appointment button. The changes that you made in Exchange will now show up in the Appointment View dialog:

Updated Appointment

Finally, choose the Delete button in the AppointmentView dialog, and the appointment will be removed from the calendar:

Delete Appointment

If you go and look at the Deleted Items folder in Outlook, you will find the deleted appointment there.

Get Appointment and Delete Appointment

Close down the AppoinmentViewer and go back to OpenEdge Architect. If you go to line 100 of the code for the AppointmentViewer.cls class, you will find the code that fetches the appointment from the server. The code determines which line was highlighted in the data grid and obtains the ItemID from the data grid. It then uses that ItemID to create a new appointment object and the Appointment class calls GetAppointment in the constructor. GetAppointment, which is in line 70 of Appointment.cls simply calls the Java Web Service to get the information.

When it is done, the AppointmentView dialog is displayed with the contents of the Appointment object.

If you right-click on the AppointmentView (not AppointmentViewer) class in the Resources view and you choose "Open With…->OpenEdge Visual Designer", you will get the AppointmentView dialog opened in OpenEdge Architect:

Appointment View Dialog

If you now double-click the Delete button, you will see the code that calls DeleteAppointment on the Appointment class. In the Appointment class, this code simply calls the DeleteAppointment operation on the Java Web Service.

Summary

That's it. The code that does all of this is fairly simple and straight-forward, once you understand what it is doing. Most of the complexity associated with this code is really around the communication between Java and the Exchange Server. The OpenEdge connectivity is reasonably simple because the Java code handles the complex work.

This is just the beginning of a prototype that we will expand on over the next few articles. I hope this has been helpful and interesting. Feel free to dig into the code that is supplied. 

In the next article, we will dive into Exchange Impersonation.

GD Star Rating
loading...