Archive for December, 2009

Part III: JFreeChart “Gantt” schedule

I was contemplating whether to manually draw the schedule, to use a JTable, or use JPanels in a GridbagLayout. I did think of JFreeChart, but I’m not sure if there’s a chart suitable, and I was hoping its Gantt chart was up to the job. It was.

The below sample shows it can render multiple “tasks” in the same row, so it satisfies my needs.

TaskSeriesCollection dataset = new TaskSeriesCollection();

TaskSeries unavailable = new TaskSeries("Unavailable");
Task room1 = new Task("Meeting Room 1", 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 7, 00).getTime(), 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 18, 00).getTime());
unavailable.add(room1);

room1.addSubtask(new Task("Meeting 1",
	new GregorianCalendar(2009, Month.DECEMBER, 1, 9, 00).getTime(), 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 16, 00).getTime()));

Task room2 = new Task("Meeting Room 2",
	new GregorianCalendar(2009, Month.DECEMBER, 1, 7, 00).getTime(), 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 18, 00).getTime());
unavailable.add(room2);

room2.addSubtask(new Task("Meeting 2",
	new GregorianCalendar(2009, Month.DECEMBER, 1, 10, 00).getTime(), 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 11, 00).getTime()));

room2.addSubtask(new Task("Meeting 3",
	new GregorianCalendar(2009, Month.DECEMBER, 1, 14, 00).getTime(), 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 15, 00).getTime()));
room2.addSubtask(new Task("Meeting 4",
	new GregorianCalendar(2009, Month.DECEMBER, 1, 16, 00).getTime(), 
	new GregorianCalendar(2009, Month.DECEMBER, 1, 18, 00).getTime()));

dataset.add(unavailable);

JFrame frame = new JFrame("MeetNow!");	
// title, domain axis, range axis, dataset, legend, tooltip, urls
JFreeChart chart = ChartFactory.createGanttChart("", "Room", "Time", dataset, false, true, false);
ChartPanel chartPanel = new ChartPanel(chart);
frame.getContentPane().add(chartPanel, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setBounds(50, 50, 800, 200);
frame.setVisible(true);

The result of the above code is like this.
gantt

Using a set of models I hooked up this UI to the query code. For some chart customization I added the meeting subject onto the bar and tooltip, changed the color and added some controls on the top.
gantt2

See Part II for querying Exchange for the schedule, and Part I for setting up the Web Service.

Comments (4)

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<GetUserAvailabilityResponseType> responseHolder = 
    new Holder<GetUserAvailabilityResponseType>();

service.getUserAvailability(request, responseHolder);

List<FreeBusyResponseType> 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.

Comments

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.

...
   <wsdl:service name="ExchangeServices">
     <wsdl:port name="ExchangeServicePort" binding="tns:ExchangeServiceBinding">
       <soap:address location="https://my.exchange.com/EWS/Exchange.asmx"/>
     </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

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<ResolveNamesResponseType> responseHolder = 
    new Holder<ResolveNamesResponseType>();

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.

Comments (3)