Mail and RDF

Warning: The content of this article may be out of date. It was imported from mozilla.org and last updated in 1999.


Mozilla Mail exposes many of it's data structures to RDF through a few datasources. This allows exposure of mailnews-specific data to user interface using RDF Templates.

You should learn about RDF before reading this document or you will be hopelessly confused.

Overview of Mail RDF graph

The root resource for all accounts, folders and messages is the RDF Resource named msgaccounts:/. From this resource, you can follow a number of arcs to find servers, folders, and finally messages. Eventually we'll probably hang mail filters, annotations, etc, off of nodes in the graph. Here is an example of how this might be set up:

In this tree-style representation of an RDF graph, I've made arcs italic and resources bold.

msgaccounts:/
    +-- http://home.netscape.com/NC-rdf#child -->
    |     imap://alecf@imap.mywork.com
    |   +-- http://home.netscape.com/NC-rdf#IsServer --> "true"
    |   +-- http://home.netscape.com/NC-rdf#child -->
    |         imap://alecf@imap.mywork.com/INBOX
    |       +-- http://home.netscape.com/NC-rdf#TotalMessages --> "4"
    |       +-- http://home.netscape.com/NC-rdf#IsServer --> "false"
    |       +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |       |     imap_message://alecf@imap.mywork.com/INBOX#1
    |       +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |       |     imap_message://alecf@imap.mywork.com/INBOX#2
    |       +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |       |     imap_message://alecf@imap.mywork.com/INBOX#3
    |       +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |             imap_message://alecf@imap.mywork.com/INBOX#4
    |       etc...
    +-- http://home.netscape.com/NC-rdf#child -->
    |     mailbox://alecf@pop.mywork.com
    |   +-- http://home.netscape.com/NC-rdf#IsServer --> "true"
    |   +-- http://home.netscape.com/NC-rdf#child -->
    |         mailbox://alecf@pop.mywork.com/INBOX
    |       +-- http://home.netscape.com/NC-rdf#TotalMessages --> "2"
    |       +-- http://home.netscape.com/NC-rdf#IsServer --> "false"
    |       +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |       |     mailbox_message://alecf@pop.mywork.com/INBOX#1
    |       +-- http://home.netscape.com/NC-rdf#MessageChild -->
    |             mailbox_message://alecf@pop.mywork.com/INBOX#2
    |       etc...
      

There are of course many more properties that are exposed via RDF, but this should give you a feel for it.

Datasources

We have a few primary datasources used in mail:

  • nsMsgAccountDataSource - answers queries about arcs coming out of msgaccounts:/. If you ask it for #child nodes out of msgaccounts:/, it returns the root URIs of all servers in the account manager. Note that it does not actually know anything about the servers who's URIs it returns.
  • nsMsgFolderDataSource - answer queries about any mail folders, including toplevel server folders. If asked about a folder along the #child arc, it will return resources for all subfolders in that folder. If asked about a folder along the #MessageChild arc, it will return resources for all messages in a folder. It also answers queries about various properties of folders such as the total number of messages, whether or not this folder is actually a root server, and so forth.
  • nsMsgMessageDataSource - answers queries about messages in folders. All RDF properties of a message currently come from the database that backs the containing folder.

Datasources and the UI

Mail does not link the UI to datasources in a "traditional" manner.

Instead of having a singleton datasource that is shared across all UI components, we have per-view datasources. This allows each template-based widget to maintain view/window-specific data with each datasource. For example...??? (sorting? what else do we store?)

Datasources are created when each window's JavaScript is loaded by declaring the datasource variables in the source javascript as global variables. In the document's onload= handler the datasources are attached to their respective widgets by setting the database property on each RDF template's parent element.

Reflecting data to RDF

In order to have a dynamic UI that updates when the underlying content changes, a datasource must implement two key methods of reflecting data into RDF.

  • Answering queries: When RDF asks for information about a resource, datasources answer with the results of the query.
  • Asynchronously notifying RDF when the data itself changes, RDF needs to be notified.

The details

Answering Queries

Mail uses RDF Resource Factories to attach mail-specific information to RDF resources. (The details of RDF Resource Factories will be left to RDF documentation for now.) From an RDF Resource, it is possible to QueryInterface() to the appropriate mail/news object, and then access information from there.

For example, the folder pane needs to display the number of messages in the INBOX. Information for this column is queried when the tree's RDF Template calls the folder datasource's GetTarget() method. The query's target is the resource named mailbox://alecf@pop.myisp.com/INBOX and the property node is named http://home.netscape.com/NC-rdf#TotalMessages. This is basically what happens, behind the scenes:

    var target = RDF.GetResource("mailbox://alecf@pop.myisp.com/INBOX");
    var property = RDF.GetResource("http://home.netscape.com/NC-rdf#TotalMessages");
    var resultNode = dataSource.GetTarget(target, property, true);
    

In the folder datasource's GetTarget(), target would be QueryInterfaced to a nsIMsgFolder. To get the total messages, the datasource would then call nsIMsgFolder.GetTotalMessages(). Finally, it would convert the result of this call to an RDF Literal, and pass it back through the return parameter of GetTarget().

An example of how this might work inside the datasource:

    var msgCountArc = RDF.GetResource("http://home.netscape.com/NC-rdf#TotalMessages");

    function GetTarget(target, property, unused) {
       var folder = target.QueryInterface(Components.interfaces.nsIMsgFolder);
       if (property == msgCountArc) {
           var msgCount = folder.GetTotalMessages(false);
           var result = RDF.GetLiteral(msgCount.toString());
           return result;
       }
    }
    

Asynchronously notifying RDF

When a mail object's data changes and the data is reflected in RDF by notifying all of the observers that RDF has registered with the datasource.

In the example of mail folders, each folder datasource first registers itself with the mail session as a nsIFolderListener because it wants information about when a folder changes. Each template registers itself as an RDF observer. When a folder's contents or properties change, it tells the mail session to notify the folder listeners that the data has changed. The folder datasource then translates these property changes into OnAssert() or OnUnassert() calls to the observers.

The calling chain essentially looks like this:

Registration:

  1. Folder datasource registers itself with the mail session as a folder listener
  2. RDF Template registers itself with the datasource as a content observer.

Notification:

  1. Folder data changes.
  2. Folder notifies mail session that it's data changed.
  3. Mail session notifies folder listeners that the folder has changed.
  4. Folder datasource notifies RDF Content observers of the changes.
  5. Content observers update UI.

An aside: Rational behind the design

After reviewing this design, it might seem unnecessary to have the double-levels of notification/registration. Why can't folders directly notify the RDF Content observers when things change?

Here is the rational behind this design:

  • It keeps all RDF datasource-related code in one place and out of the messages and folders themselves. This allows the mail code to be mostly free of RDF. The actual dependancies on RDF are small, so it makes sense to keep RDF out of folders and messages.
  • There should be a non-RDF mechanism for folder change notification. There are times (for example, from JavaScript/XUL) where we want folder notifications that have nothing to do with RDF. Requring these listeners to use RDF would be unnecessarily burdening them with an unnecessary API.
  • The folders or the mail session would have to know about every RDF observer on each datasource. This means the folder would have to notify each of those observers. This design allows each datasource to manage its own observers, and lets each mail session manage a list of folder listeners without necessarily knowing that some of these listeners are RDF-related.

Please

comment

!


Alec Flett

Last modified: Thu Oct 7 11:33:42 PDT 1999