RDF Datasource Details

This section will go into detail about some of the basic datasources provided by Mozilla.

Basic Datasources

As described in the previous section, Mozilla provides three basic forms of datasource. These basic datasources are the in-memory-datasource, the xml-datasource and the composite-datasource. These are described in more detail below. In addition, several datasources are provided which are used for storing particular types of data. For example, Mozilla provides datasources for bookmarks, history, installed search engines, mail folders and so on. Some of these datasources are actually wrappers around the three basic types. For example, the bookmarks datasource is a wrapper around an in-memory-datasource. When a change is made to the bookmarks, it actually ends up calling the inner in-memory-datasource to hold the data. This is done because this inner datasource has no limitation on what kind of data it can hold. The bookmarks datasource however needs to enusre that the data entered is valid within the context of bookmarks. Other datasources, such as the history datasource, are wrappers around other internal data structures.

Memory Datasources

The in-memory-datasource holds all of the RDF data in memory. It is designed to be efficient for querying and modification. This is the type of datasource that you would use for custom data that isn't loaded from an RDF/XML file.

The memory datasource implements all of the methods of the nsIRDFDataSource interface as all datasources do. It also implements the nsIRDFInMemoryDataSource interface which provides a single method EnsureFastContainment. You wouldn't normally call this method yourself. It's used internally for optimizing storage. Basically, it's used to ensure a particular resource in the datasource is stored in such a way that access to it is faster. The tradeoff is that more memory is used. RDF containers set this automatically when they have a large number of children, as access would be slower otherwise.

XML Datasources

The xml-datasource is used for data loaded from an RDF/XML file. These files may be local files or remote files stored on a web site. These datasources can be reloaded and saved as well. Loading an RDF/XML file is as simple as calling the GetDataSource method of the RDF service. This will load the file and parse it into a datasource. The xml-datasource is actually implemented as a wrapper around the in-memory-datasource. This inner datasource holds the RDF data.

The RDF/XML file must have a HTTP content type of text/xml, application/xml or text/rdf. Mozilla does not currently support RDF/XML sent as application/rdf+xml. RDF/XML sent as any other content type than the above will not load into a datasource. When loading datasources using the RDF service, you should always specify the absolute URL of the RDF/XML file, not a relative URL.

RDF/XML datasources may be loaded from any type of URL. Currently, only those loaded from file URLs (URLs that begin with 'file:') may be modified with the RDF modification APIs. One possible workaround for modifying remote RDF sources is to load the RDF and then add the data into a separate in-memory-datasource.

The GetDataSource method loads the file asynchonously. This means that the datasource may not be loaded after the method returns. A synchronous load may be done with the GetDataSourceBlocking method. This method will wait until the data has loaded before returning. Note that this will appear to hang the user interface while waiting for the datasource to load.

It can be useful to use asynchonous loading yet still determine when the datasource has loaded. The xml-datasource implements an interface nsIRDFXMLSink which is a helper interface called during RDF loading and parsing. For the most part it is used internally, but the interface may be used to add an observer which will be notified when the RDF/XML has been loaded. Here is an example:

var observer = {
  onBeginLoad : function(sink){},
  onInterrupt : function(sink){},
  onResume : function(sink){},
  onError : function(sink,status,msg){},
  onEndLoad : function(sink){
    sink.removeXMLSinkObserver(this);
    sink.QueryInterface(Components.interfaces.nsIRDFDataSource);
  }
};
var ds=rdfService.GetDataSource("http://www.xulplanet.com/tutorials/xultu/animals.rdf");
ds.QueryInterface(Components.interfaces.nsIRDFXMLSink);
ds.addXMLSinkObserver(observer);

The addXMLSinkObserver method is used to add an observer to the datasource load process. The observer needs to implement the nsIRDFXMLSinkObserver interface which in the example is implemented by a JavaScript object. Of particular interest is the onEndLoad method, which will be called when the data has been fully loaded. In this method, we take the opportunity to remove the observer using the removeXMLSinkObserver method. This is where you would add code to execute after the datasource has loaded. Note that the datasource may be casted to and from the nsIRDFXMLSink directly.

The xml-datasource also implements the nsIRDFRemoteDataSource interface. Despite the name, this interface is available for both local files and remote content. This interface contains methods to reload and save the datasource. Saving datasources will be discussed in a later section.

To reload a datasource, call the Refresh method. It takes one argument, whether to block while loading or not. If true, the method will wait until the datasource has fully loaded before returning. If false, the method will return immediately. The example below will get a datasource and reload it.

var ds=rdfService.GetDataSource("file:///main/data/animals.rdf");
ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
ds.Refresh(true);

Parsing RDF/XML From a String

You can also parse RDF/XML from a string. This involves creating an RDF/XML parser component and supplying a datasource to parse into. The xml-datasource has the ability to parse into itself, but the parser can add the data to any modifyable datasource. The parser uses the modification methods of the nsIRDFDataSource interface, so all that's required is a datasource which handles these methods appropriately.

The interface nsIRDFXMLParser has a method which may be used to parse a string of RDF/XML. In this example, we parse the data into an in-memory-datasource.

function parseRDFString(str, url)
{
  var memoryDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                   .createInstance(Components.interfaces.nsIRDFDataSource);
  var ios=Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);
  baseUri=ios.newURI(url,null,null);
  var parser=Components.classes["@mozilla.org/rdf/xml-parser;1"]
                       .createInstance(Components.interfaces.nsIRDFXMLParser);
  parser.parseString(memoryDS,baseUri,str);
  return memoryDS;
}

This function can be broken into three parts. The first part creates a new empty datasource. The second part creates a URI object, since all datasources need to have a URI. Since we are parsing from a string, we don't have any specific URI, so we might just make one up. Any relative references in the RDF/XML will be resolved relative to this URI. Finally, the RDF/XML parser is created and we parse the content using the parseString method. This method takes three arguments, the datasource to parse into, the base URI created in the second part and the string to parse.

If you pass an xml-datasource to the parseString method, the new data will replace the existing data. For other datasources, however, such as an in-memory-datasource, the new data will be added to whatever is already in the datasource. You might use this to build up a larger datasource from several small ones.

When the data is replaced in an xml-datasource, some special handling is done to ensure that the newly parsed data doesn't delete data if it already exists. When the parse occurs, the existing RDF statements in the datasource are kept at first. When a new statement is added from the new parsed data, and it already exists, the old statement is kept instead. If the new statement doesn't already exist, it is added. Once parsing is complete, statements that existed in the old data but are not in the new data are cleaned out. This unusual process is used to ensure that the resource objects aren't deleted and recreated, which may have undesirable side effects. This process involves the use of the nsIRDFPurgeableDataSource interface. This internal interface is intended to be used only for this purpose -- you shouldn't use it yourself.

The parseString method loads synchronously. There is also a parseAsync method which can be used to parse asynchronously. The takes the datasource and the baseURI and returns an object which implements the nsIStreamListener interface. You will need to call the methods of this interface and pass the RDF to it. This is a bit awkward, so you probably wouldn't use this method.

Composite Datasources

The composite-datasource holds a list of other datasources. When you query this datasource, it will query each of the datasources in the list in turn until a response is found, which will then be returned. If you attempt to change the composite-datasource, it will call each of the datasources in its list until one of them accepts the change. For query methods that only return a single value, or for modification methods, only one datasource will return results or get changed. Once a datasource is found with the result or that will accept a change, the others are not queried. For query methods that normally return multiple values, all of the possible values in all of the datasources are returned. Naturally, you can still query and modify the individual datasources separately.

Since the composite-datasource holds a list of datasources, it effectively can be used as if all the datasources it contains were combined together into a single datasource. This combining is sometimes called aggregation. Any datasource can be added to a composite-datasource, either RDF/XML sources, built-in Mozilla datasources and even other composite datasources.

The datasource added to an XUL element when it uses a datasources attribute is a composite-datasource, so you can add datasources and remove them at any time.

The nsIRDFCompositeDataSource interface is implemented by the composite-datasource and is used to add and remove the datasources. To add a datasource, use the AddDataSource method. It takes one argument, the datasource to add. You should pass the datasource itself, not its URI. Note that this method does not check for uniqueness. If the datasource is already included in the list, it will be added again. The following code is an example of adding a datasource:

var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                   getService(Components.interfaces.nsIRDFService);
var compositeDS = rdfService.GetDataSource("rdf:composite-datasource");
var ds1=rdfService.GetDataSource("http://www.xulplanet.com/tutorials/xultu/animals.rdf");
var ds2=rdfService.GetDataSource("rdf:bookmarks");
compositeDS.QueryInterface(Components.interfaces.nsIRDFCompositeDataSource);
compositeDS.AddDataSource(ds1);
compositeDS.AddDataSource(ds2);

When you query the composite datasource, it will query the first datasource, in this case an RDF/XML source, followed by the second, the bookmarks datasource, until a result is found.

You can remove a datasource from the composite, by calling the RemoveDataSource method. It takes one argument, the datasource to remove.

The GetDataSources method may be used to get a list of the datasources used by the composite. This method returns an enumeration which can be used to iterate over the datasources. They will be returned in the order in which they were added. In the following example, we retrieve the datasources that are attached to a XUL element. These are the datasources that the template would use.

var compositeDS = xulElement.database;
var list = compositeDS.GetDataSources();
while (list.hasMoreElements()){
  var ds = list.getNext();
  if (ds instanceof Components.interfaces.nsIRDFDataSource){
    ...
  }
}

The composite datasource interface has two properties. The first, allowNegativeAssertions is used to indicate how the composite handles negative assertions. A negative assertion is an RDF statement which the datasource specifies is false. If this attribute is true, the default value, the composite datasource will handle negative assertions. If one datasource contains a normal true statement, and another contains the same statement but is false, they will cancel each other out. If the allowNegativeAssertions property is false, the datasources are not checked for negative statements. This is faster, so you may wish to use change the value if you know that the datasources don't contain any negative assertions. Most datasources don't.

The coalesceDuplicateArcs property indicates whether the composite datasource will remove duplicates when queries are made. If this value is true, and a query is made, duplicate values will be removed. This is useful when combining several datasources so you don't have to worry about whether multiple datasources contain the same data. If this property is false, duplicates will be returned, which will result in slightly more optimal queries.

The Local Store

The datasource rdf:local-store is included with Mozilla and is used to hold state information such as the position of the browser window, which columns in tree views are displayed, and which toolbars and sidebars are displayed. This information is saved when Mozilla exits, and is re-applied automatically to the XUL content when the appropriate window is opened again. This process is explained in detail in the section on Persistent Data. To summarize, the persist attribute may be used on a XUL element to save data in the local store and have it restored when the XUL window is opened later. Although the local store normally holds XUL state information, you can actually put anything you want into it.

The local store is saved to an RDF/XML file 'localstore.rdf' in your Mozilla profile directory. Since it's an RDF/XML file, you can open it in a text editor and view the information it contains. It's possible to modify the file as well, although that isn't recommended unless you know what you're changing.

The local-store is always included in a chrome XUL application when you use a datasources attribute on an element. The local store is always the first datasource used. Of course, nothing prevents you from removing it later using the composite datasource's RemoveDataSource method.

This means that it's possible to add information to the local store which overrides information in other datasources used in the template. For example, if you add the right information to the file localstore.rdf, you could make it appear as if the user had an additional bookmark, since Mozilla uses a template to display the bookmarks list to the user. Of course, the bookmark won't work properly since it's not really in the bookmarks datasource.

Add a note User Contributed Notes
No comments available

Copyright © 1999 - 2005 XULPlanet.com