Part II: Query Exchange availability with Java

Although very verbose, I’m just listing the minimum you need to run the query. If you use it you will likely need to use utility methods to modularize the code and alter the input.


ExchangeServices factory = new ExchangeServices();
ExchangeServicePortType service = factory.getExchangeServicePort();

// create request
GetUserAvailabilityRequestType request = 
    new GetUserAvailabilityRequestType();

// set timezone
SerializableTimeZoneTime standardTime = new SerializableTimeZoneTime();
standardTime.setTime("00:00:00");
standardTime.setDayOrder((short)1);
standardTime.setDayOfWeek(DayOfWeekType.SUNDAY);

SerializableTimeZone timezone = new SerializableTimeZone();
timezone.setBias(-8 * 60);	// CHANGE THIS TO YOUR TIMEZONE
timezone.setStandardTime(standardTime);
timezone.setDaylightTime(standardTime);
request.setTimeZone(timezone);

// set time window of meeting
try {
  DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
  Duration duration = new Duration();
  // CHANGE THESE DATES
  duration.setStartTime(datatypeFactory.newXMLGregorianCalendar(
    new GregorianCalendar(2009, Month.DECEMBER, 8, 7, 00)));
  duration.setEndTime(datatypeFactory.newXMLGregorianCalendar(
    new GregorianCalendar(2009, Month.DECEMBER, 8, 18, 00)));

  FreeBusyViewOptionsType options = new FreeBusyViewOptionsType();
  options.setTimeWindow(duration);
  options.getRequestedView().add("Detailed"); // retrieve subject info

  request.setFreeBusyViewOptions(options);
} catch (DatatypeConfigurationException e) {
  e.printStackTrace();
  System.exit(-1);
}

// set meeting rooms to check
EmailAddress emailAddress = new EmailAddress();
// CHANGE THIS TO YOUR ROOM
emailAddress.setAddress("MeetingRoom@my.exchange.com");

MailboxData mailbox = new MailboxData();
mailbox.setEmail(emailAddress);
mailbox.setAttendeeType(MeetingAttendeeType.REQUIRED);
ArrayOfMailboxData mailboxes = new ArrayOfMailboxData();
// ADD MULTIPLE ROOMS IF NEEDED
mailboxes.getMailboxData().add(mailbox);
request.setMailboxDataArray(mailboxes);

// create response
Holder responseHolder = 
    new Holder();

service.getUserAvailability(request, responseHolder);

List responses = 
    responseHolder.value.getFreeBusyResponseArray()
    .getFreeBusyResponse();

for (FreeBusyResponseType response: responses) {
  ArrayOfCalendarEvent events = 
    response.getFreeBusyView().getCalendarEventArray();
  if (events == null) continue;
  for (CalendarEvent event: events.getCalendarEvent()) {
    System.out.printf("%s - %s : %s\n", event.getStartTime(), 
      event.getEndTime(), event.getCalendarEventDetails().getSubject());
  }
}

This code will simply print out if there are conflicting events in the “time window” you specified for the given “rooms”. If you use people as the addresses you will be checking the availability of the people. If you don’t use “Detailed” you will just get the conflicting times. There will be one FreeBusyResponseType for each address you gave as input, in the same order. If there are no conflicts, events will be null.

See Part I for how to create the stubs, and Part III for the JFreeChart GUI.

Part I: Java -> MS Exchange Server

To assist my laziness I tried to write a utility that helps me check the available meeting rooms for booking faster than having to go to Outlook’s Calendar’s Scheduling Assistant.

Luckily, I found that my mail server has enabled Web Services, which should make my job much easier. The WSDL can be found at “https://my.exchange.com/EWS/exchange.asmx”. I tried to use Axis to generate the stubs, but hit an error.

{http://schemas.microsoft.com/exchange/services/2006/types}
ReminderMinutesBeforeStartType>null already exists

I searched and couldn’t find anything. Not wanting to dig into the implementation, I tried to switched to JAXWS. Not going well either.

At least one WSDL with at least one service definition needs to 
be provided.

This time I found the answer here. In fact I hit all the same problems as described even after solving this. I’ll repeat the solution here because I realize linking is good, but the sites might get torn down after some time.

Download the Services.wsdl, messages.xsd and types.xsd to your local disk. Edit Services.wsdl to add the service definition manually.

...
   
     
       
     
   

With this added I generated the stubs. I used the “-Xnocompile” option to keep the .java files, so I copied them into Eclipse directly. This will be needed as we need to hack the source later.

I tried to call a simple API first – to resolve a name, or “Check Names”. How do I know what to call? Read the documentation.


ExchangeServices factory = new ExchangeServices();
ExchangeServicePortType service = factory.getExchangeServicePort();

// prepare request		
ResolveNamesType request = new ResolveNamesType();
request.setUnresolvedEntry("francis");

// prepare response
Holder responseHolder = 
    new Holder();

service.resolveNames(request, responseHolder);

ResolveNamesResponseMessageType response = 
    (ResolveNamesResponseMessageType) responseHolder.value.getResponseMessages()
    .getCreateItemResponseMessageOrDeleteItemResponseMessageOrGetItemResponseMessage()
    .get(0).getValue();
for (ResolutionType resolution: response.getResolutionSet().getResolution()) {
  System.out.printf("%s: %s", resolution.getMailbox().getName(), 
    resolution.getMailbox().getEmailAddress());
}

I hit the isNil problem also as described in the guide, so just comment out the fields that you are not using in ExchangeServicePortType such that you are left with the request and response.

This example is meant to demonstrate how to access Exchange with Java Web Services. If you use it you will likely need to handle errors, exceptions, nulls and the like.

Part II will show how to query the resource availability, and Part III shows how to display this results graphically using JFreeChart.

saving a onscreen component to image file

I have a custom JPanel with the paintComponent() overridden to draw something custom. To export it to an image file, I drew the component into another graphics object.


JPanel custom = ...;
BufferedImage image = new BufferedImage(...);
custom.paint(image.getGraphics());

After this the image would have contained what it looks like on screen. The next step is straightforward: save the image:

ImageIO.write(image, "png", file);

Additional tip: Knowing how Swing render lightweight components is very useful.

Custom JFileChooser

The default JFileChooser doesn’t prompt to overwrite files when saving. It also doesn’t automatically add an extension to the file if the user didn’t type one. I had to do a custom one to support these features for better usability.

The limitation of this specific implementation is it only supports one “image” extension, and it’s not i18n-ed. Customize it more for your own needs.

private class CustomFileChooser extends JFileChooser {
  private String extension;
  public CustomFileChooser(String extension) {
    super();
    this.extension = extension;
    addChoosableFileFilter(new FileNameExtensionFilter(
      String.format("%1$s Images (*.%1$s)", extension), extension));
  }

  @Override public File getSelectedFile() {
    File selectedFile = super.getSelectedFile();

    if (selectedFile != null) {
      String name = selectedFile.getName();
      if (!name.contains("."))
        selectedFile = new File(selectedFile.getParentFile(), 
          name + '.' + extension);
    }

    return selectedFile;
  }

  @Override public void approveSelection() {
    if (getDialogType() == SAVE_DIALOG) {
      File selectedFile = getSelectedFile();
      if ((selectedFile != null) && selectedFile.exists()) {
        int response = JOptionPane.showConfirmDialog(this,
          "The file " + selectedFile.getName() + 
          " already exists. Do you want to replace the existing file?",
          "Ovewrite file", JOptionPane.YES_NO_OPTION,
          JOptionPane.WARNING_MESSAGE);
        if (response != JOptionPane.YES_OPTION)
          return;
      }
    }

    super.approveSelection();
  }
}

Dump XML in Java

In this example, node is the XML node (which can be a document) you want to dump. You’ll see the XML in System.out. If you like the output in a String (e.g. for logging), use a StringWriter as the output.


Transformer serializer = TransformerFactory.newInstance().newTransformer();
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.transform(new DOMSource(node), new StreamResult(System.out));

code stench

Avoid referencing resources by absolute paths as they are likely to end up in jars. Depend on a delegated strategy which uses Class.getResourceAsStream().

Avoid using i18n for non language purposes. It should be used solely for text shown on the screen. Code properties should be kept in property files. And vice versa.

Eclipse Remote Debugging

Eclipse has a powerful debugger, which allow breakpoints, code stepping, variable inspection, expressions, hot code replacement etc. Therefore any application that is run from Eclipse can be debugged.

Even if it is a built and released application running on a separate machine, it can also be debugged remotely from an Eclipse IDE. As this option is built into the JVM, it can work on any kind of Java application; from simple Java applications to web applications to complex Eclipse RCP plugins.

Just add these JVM options to the command that starts the application/web server.

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1234

When the application is started, it will wait for a debugger to be attached.

From Eclipse, Open the Debug dialog, and create a new “Remote Java Application”. Set the host and port in the “Connection Properties” section to the waiting application. Click “Debug” and watch it go! Set the breakpoints and it can be debugged as if it was locally run.