GroupCal as a Web Component

Suppose we did outfit GroupCal with meeting and task objects. Since these things are more objectlike than relational, XML might be a good way to represent them. An XML-capable object database could serve up this data; XSL presentation services could render it in a browser; DHTML scripting could support interactive viewing and editing.

This is all very exciting but slightly ahead of the curve in terms of the installed base. Netscape is playing a catch-up game with XML; browser support for XSL is still experimental; the Microsoft and Netscape DHTML technologies are quite different. For the time being, web applications that manage complex data objects within a calendar framework rely on server-side production of HTML. This approach is time-honored (as Internet time goes, that is) but still really useful.

As we saw with the Microsoft Index Server (Chapter 8), one way to work with a software component that exports a web API is to treat it as a black box accessible only through that API. Anything that a user can do by way of a browser, a web-client script can do by manipulating URLs. This duality, inherent in all web software, can often enable startlingly simple solutions to seemingly hard problems.

Importing Data into GroupCal Using its Web API

When I first deployed GroupCal, a colleague asked if he could import data from his personal information manager (PIM) into GroupCal. My first response was: no way! But on reflection I saw that this was actually an easy thing to do. To load data into GroupCal, a web-client script need only issue a series of HTTP requests like this:

/GroupCal?who=Jon+Udell&update=append&Fri+Jun+19+1999=remember+to+call+mom

GroupCal already had an implied import capability, just by virtue of the fact that it could accept data from browsers. The program itself didn’t need any new import-related features. All that was needed was a web-client script that could issue a set of URLs, and one converter per PIM. The job of a converter is to morph calendar data extracted from the PIM into the set of URLs that is GroupCal’s de facto import format.

To illustrate this idea, we can use any calendar product that exports an accessible format. Here, for example, are a couple of calendar entries in comma-separated variable (CVS) format, similar to what Netscape’s Calendar can export:

"Jon Udell","06-19-1999","09:00","09:15","15","Remember to call mom"
"Jon Udell","06-19-1999","14:30","15:00","30","Staff mtg"

Example 10.4 shows a Perl script that loads these entries into GroupCal.

Example 10-4. A GroupCal Import Script

#! /usr/bin/perl -w

use strict;

use LWP;
use Date::Manip;
use TinyCGI;

my $tc = TinyCGI->new();

&Date_Init("TZ=EST");                                # initialize Date package

my $user = "Jon+Udell";

open (F, "import.csv")                               # open import file
  or die "cannot open import file $!";  

my $headers = <F>;
my $field = ""([^"]+)"";                          # define field pattern
while (<F>)
  {
  m#$field,$field,$field,$field,$field,$field#;      # match fields
  my ($date, $entry) = ($2,$6);                      # extract date and entry
  $date =~ m#(d+)-(d+)-(d+)#;                     # extract date fields
  my ($mm, $dd, $yyyy) = ($1, $2, $3);               # save date fields
  my $weekday  = 
    $ Date: :Manip: :Week[& Date_DayOfWeek($ mm,$ dd,$ yyyy)-1];    # get weekday
  my $ month    = $ Date::Manip::Month[$ mm-1];         # get month
  $month    = substr($month,0,3);                    # format month
  $weekday  = substr($weekday,0,3);                  # format weekday
  $date     = "$weekday+$month+$dd+$yyyy";           # construct date
  $entry = '<hr>' . $entry;                          # format entry
  my $url = sprintf                                  # format URL
    ("http://localhost:9090/GroupCal?who=%s&update=replace&%s=%s
", 
    $user, $date, $tc->escape($entry) );
  pokeCal($url);                                     # transmit URL
  }

sub pokeCal
  {
  my ($url) = @_;
  my $ua = new LWP::UserAgent;                       

  my $request = new HTTP::Request 'GET';             # new Request object

  $request->header                                   # set credentials
    ('Authorization', "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");

  $request->url($url);                               # set URL

  print my $response = $ua->request($request);       # transmit request
  }

Perl’s LWP module provides the HTTP client services needed to access GroupCal. In Chapter 8 we saw how you can write a one-line URL-fetcher using LWP::Simple. Why not do that here? Because GroupCal implements HTTP basic authentication, the importer shown in Example 10.4 has to transmit login credentials along with its HTTP requests. That’s more than LWP::Simple alone can do, so the script also taps LWP::UserAgent and HTTP::Request. We’re taking a shortcut by hardcoding the MIME-encoded user/password pair required by GroupCal ’s authentication scheme. A more robust version might prompt the user for these credentials, then use Perl’s MIME::Base64 module to encode them into the form required by HTTP basic authentication.

The GroupCal import script uses another Perl module that we haven’t encountered yet. Date::Manip provides a mechanism to convert from MM-DD-YYYY format to GroupCal ’s “Fri Jun 19 1999” style. Finally, TinyCGI provides the escape( ) function that translates characters not allowed in URLs (e.g., the double quote) into an alternate form (e.g., %22).

It’s a snap to construct GroupCal URLs for each record in the CSV export file. The update=append part of the URL tells GroupCal to append this data to the specified entry rather than replacing it, as does the alternate update=replace form used by GroupCal ’s interactive edit form.

Exporting Data from GroupCal Using its Web API

If GroupCal ’s web API can import data from a CSV export file, can it also export to another calendar? Sure. Again the work is mostly already done. GroupCal ’s viewer emits neatly structured HTML pages that are a snap to parse using simple regular expressions. There is a potential conflict between GroupCal-written HTML, which creates a table cell for each daily entry, and user-written HTML, which could be used to nest a table within an entry’s cell. Distinguishing between system-generated and user-written HTML tables could be tricky. Fortunately we can cheat by wiring GroupCal to emit distinctive table syntax. Since GroupCal assumes a dumb HTML viewer, not a smart XML/XSL viewer, we can recruit our old friend the CSS class attribute for this job. Here’s a fragment of generated HTML that shows how the class attribute can distinguish a GroupCal-written table cell from a user-written table cell:

<td class="gcEntry">
...
</td><!-- end gcEntry -->

It’s not really necessary to use the class attribute here. Any arbitrary attribute can play the same role. The class attribute is legal HTML, though, which matters if you ever need to validate against an HTML DTD. And as we’ve seen, it doubles as a hook for CSS styles. While we’re at it, let’s have GroupCal emit each of these special tags between newline boundaries. That makes no difference to browsers but a world of difference to the line-oriented export script shown in Example 10.5. When you can work both sides of the street as we’re doing here, it makes sense to adjust web APIs for the convenience of scripts that use them.

To vary the example, let’s use the vCalendar format (see http://www.imc.org/pdi/vcal-10.txt), a standard that’s used in a number of calendar products. The script in Example 10.5 reads a GroupCal web page and writes a vCalendar import file that any vCalendar-aware application can read.

Example 10-5. A GroupCal Export Script

#! /usr/bin/perl -w

use strict;
use Date::Manip;

&Date_Init("TZ=EST");

open (F, "groupcal.html") or die "cannot open $!";    # open saved GroupCal page
print "BEGIN:VCALENDAR
";                            # begin vCalendar file
while (<F>)
  {
  if ( m#<td class="gcEntry"# )                     # match GroupCal entry
    {
    m/(w{3})+(w{3})+(d{2})+(d{4})[#w]*>(w{3}) (w{3}) (d{2})/;
    my $date = &DateCalc("$6 $7 $4");                 # extract date
    my $yyyy = substr($date,0,4);
    my $mm = substr($date,4,2);
    my $dd = substr($date,6,2);
    $date = "$yyyy$mm$dd";                            # reconstruct date
    my $dtstart = $date . 'T100000Z';                 # fabricate an appt time
    my $dtend = $date . 'T101500Z';
    my $entry = "";
    while (<F>)                                       # read in the entry
      {
      chomp;
      last if ( m#</td><!-- end gcEntry --># );
      $entry .= $_;
      }
    if ( $entry ne '' )
      { 
      $entry =~ s/<[^>]+>/ /g;                        # de-HTMLize the entry
      print <<"EOT";                                  # emit vCalendar event
BEGIN:VEVENT
DTSTART:$dtstart
DTEND:$dtend
SUMMARY:$entry
DESCRIPTION:$entry
END:VEVENT
EOT
      }
    }
  }

print "END:VCALENDAR
";                              # close vCalendar file

As is typical of such transformations, the mapping isn’t a perfect one. The SUMMARY field, which appears in Netscape Calendar’s daily, weekly, and monthly views as the title of each entry, is limited to a short string of characters. The DESCRIPTION field doesn’t truncate, but users have to drill down into an entry to view it. The compromise solution here is to put each GroupCal entry in both places.

Since Calendar doesn’t render HTML tags, this filter eliminates them. Finally, since a vCalendar entry isn’t valid without start and end times, the import script pretends that each GroupCal entry is a 6 A.M. appointment.

If GroupCal really wanted to be a group scheduler and todo list manager, rather than just a shared bulletin board organized in a time-based fashion, it would need some new data structures. We’d like these structures to display in GroupCal entries but not as part of the free-form editable data. Managing appointment or todo data inside GroupCal would require a major change to the servlet. But if such data is managed in another web component, we can easily hook that component’s display and editing features into GroupCal’s existing framework. Let’s see how that can work.

Connecting a Task Database to GroupCal

We’ll assume there’s an SQL-based task database managed primarily by some other application. The task database contains the data shown in the following table.

User

Project

Task

Due

Done

Jon Udell

1

1

4/30/1999

NULL

Joy-Lyn Blake

2

1

5/1/1999

NULL

Jon Udell

2

2

4/20/1999

5/1/1999

Jon Udell

3

1

5/6/1999

NULL

Jon Udell

3

2

5/6/1999

5/6/1999

We’d like to integrate GroupCal with that task database by injecting task data into its calendar views. For example, each daily calendar entry could document the tasks due on the current day for the selected user. Today’s calendar entry could summarize all pending tasks. Each pending task appearing in a calendar entry could also include a link that, when clicked, moves the task from a pending to a completed state. In this way, GroupCal can both view and modify the task database.

An obvious solution would be to use Java’s JDBC classes to connect the servlet to the SQL task database. But just because you can do that doesn’t mean that you have to. In this case, we’ll also assume that the task-database manager exports a higher-level web API than GroupCal can use. Suppose, for example, that the task-database manager supports the API shown in Table 10.4.

Table 10-4. The Task-Database Manager’s Web API

Action

URL

Query for tasks due on today’s date (i.e., the date argument is today’s date)

/todo_view?user=Jon+Udell&date=Mon+May+04+1999

Query for tasks due on a different date (i.e., the date argument isn’t today’s date)

/todo_view?user=Jon+Udell&date=Mon+May+04+1999

Mark a pending task as done

/todo_mark_done?user=Jon+Udell&project=1&task=2

When the date argument is today’s date, the task-database manager answers the todo_view URL with an HTML-formatted table of all tasks due on or before today, highlighting past-due tasks in red. When the date argument isn’t today’s date, it answers with a table of just the tasks due on that date. Finally, the todo_mark_done URL moves the specified task from pending to completed status.

Figure 10.4 shows a weekly calendar view that uses this web API to include task information.

Task information injected into a calendar view

Figure 10-4. Task information injected into a calendar view

How do the HTML-formatted SQL results returned by the task-database manager appear in the calendar? GroupCal uses Java-style web-client programming to fetch them. Example 10.6 shows how that can work.

Example 10-6. Servlet as Web Client

public static String getTodo (  HttpServletRequest req, 
                                String who, String key, String sAuth )
  throws MalformedURLException
  {
  String sAuthHeader = req.getHeader("AUTHORIZATION");  // capture auth header
  String sCredentials = sAuthHeader.substring(6,sAuthHeader.length());
  String compare_date = key.substring(4,key.length());
  String sRet = "";
  try {                                                 // form "todo server" URL
    URL url = new URL("http://todohost/todo_view?user=" + 
        GroupCalUtil.encode(who) + "&date=" + 
        GroupCalUtil.encode(compare_date));
    URLConnection c = url.openConnection();
    c.setRequestProperty("Authorization", sCredentials); // pass credentials
    InputStreamReader in = new InputStreamReader (c.getInputStream () );
    BufferedReader buf = new BufferedReader (in);
    String s = "";
    while (true)
      {
      s = buf.readLine();
      if ( s == null ) break;
        sRet += s;
      }
    in.close();
    }
  catch ( IOException e )
    { sRet = "<p>todo server unavailable";  }
  return sRet;
  }

GroupCal calls this method once per calendar entry, passing the date for that entry. Won’t that be slow? That depends on how the task-database manager’s web API is implemented. An effective web-to-SQL adaptor can answer a flock of HTTP requests in short order. If invoking SQL queries on a per-calendar-entry basis proves unwieldy, then batch the requests into a single query and unpack its results inside the servlet.

In this kind of distributed setup, where should the authorization for the markDone function reside? Not in GroupCal. Although it uses HTTP authentication to protect calendars, GroupCal isn’t the primary manager of the task database. That responsibility lies with the “todo server.” GroupCal can, as shown in Example 10.6, pass along the credentials that it received.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.121.131