This section will describe RDF datasources in Mozilla.
About RDF Datasources
Mozilla stores RDF information in something called a datasource. Each datasource holds a set of RDF triples. You may query datasources for RDF triples using a variety of methods. This allows you to navigate through the RDF graph or determine the data that the datasource holds. Some datasources are modifyable so one may add or remove triples from the datasource. You may use the datasources independently of each other, or you may combine them together into what is called a composite datasource. The composite datasource is a group of RDF datasources.
Usually, each datasource holds data that is related in some way. For instance, Mozilla uses a bookmarks datasource which holds information about the user's bookmarks. Mozilla uses a number of datasources that hold information and if you are building a Mozilla extension, you might use these datasources. However, you can also create your own datasources. Mozilla also supports parsing RDF/XML files into datasources.
All RDF datasources implement the nsIRDFDataSource interface. This interface provides methods to query and modify the information in the datasource. Here are the most common datasources that you would use that are provided by Mozilla:
|in-memory-datasource||This datasource holds RDF triples in memory. Many of the other datasources wrap this datasource. This datasource may be modified.|
|xml-datasource||This datasource holds the triples read from an RDF/XML file. It can keep track of the file it was loaded from and it can save modifications back to this file. This datasource can also support RDF/XML loaded from a remote URL, although these cannot be modified or saved. All xml-datasources implement the nsIRDFRemoteDataSource interface, even those that are loaded from local files.|
|composite-datasource||Holds a collection of other datasources. When you query this datasource, it will query each datasource in turn. Similarly, modifications will propogate to each datasource until one of them accepts the change. These datasources also implement the nsIRDFCompositeDataSource interface.|
There are also numerous other datasources, but these hold data specific to the Mozilla application.
Getting RDF Datasources
Mozilla uses an RDF service which is responsible for getting RDF datasources. The service also returns other RDF related objects such as resource and literal objects. Datasources are identified by a URI. If you use a URI, this will usually result in the creation of an xml-datasource which will load the RDF/XML from that URI.
Mozilla provides a number of additional datasources that can be retrieved by using a URI that begins with 'rdf:'. For example, the URI rdf:history will return the history datasource which holds data related to the user's browsing history. When you request a datasource of this form, that is, a datasource with a URI that begins with 'rdf:', Mozilla will look for a component that handles that datasource. It finds this component by taking the part of the URI after the prefix and appends it to the string @mozilla.org/rdf/datasource;1?name= to form a component name. For example, the URI rdf:bookmarks, will result in the component @mozilla.org/rdf/datasource;1?name=bookmarks.
All of the datasources work this way. If you create a custom component using that naming convention, you will be able to retrieve the datasource using the RDF service. You may also use the custom datasource in a template by setting the URI on an element's datasources attribute. The 'rdf:' datasources usually have special handling of the datasource methods, or store the data in a custom manner. For example, the history datasource stores data in a custom format and uses code to wrap the data such that it can be queried using the RDF datasource APIs. The bookmarks datasource stores the data on disk in a custom format, but uses an in-memory-datasource to hold the data while in memory.
The RDF service has two methods for retrieving datasources. The first method, GetDataSource is used to load a datasource asynchronously. This function may return before the datasource has loaded, although datasources that are already loaded will be available right away. The 'rdf:' datasources are usually available immediately, as the browser will already be making use of them. Even still, 'rdf:' datasources usually do any initialization right away. This means that the GetDataSource method is suitable for loading these kind of datasources. You should also use this method to load remote RDF/XML files so that they can be retrieved in the background. A method will be explained later for determining when the load is complete.
The second method, GetDataSourceBlocking gets a datasource and waits for it to be loaded. This method may be used for local RDF/XML files. This method will return once the datasource has been loaded and parsed. This method does not currently work for remote files.
Both methods take a single argument, the absolute URI of the datasource to load, and they both return the datasource. In the case of the GetDataSource function, the datasource is returned but it may not yet contain any data. Here are some examples of both functions.
var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"]. getService(Components.interfaces.nsIRDFService); var historyDS = rdfService.GetDataSource("rdf:history"); var fileDS = rdfService.GetDataSourceBlocking("file:///somedir/somefile.rdf");
First, we get the RDF service. Since it is a service, we use getService instead of createInstance. The next line retrieves the history datasource using the GetDataSource function. Finally, we retrieve an RDF/XML file using the GetDataSourceBlocking function, which can be queried and modified immediately. Each of the returned datasources implement the nsIRDFDataSource interface.
For 'rdf:' datasources, the GetDataSource and GetDataSourceBlocking functions create the new datasource as a service. That means that only one copy of the datasource exists at a time. Be careful of this if implementing your own datasources. If you want a different datasource to be created each time, you'll need to create the component the usual XPCOM way with the createInstance method. For instance, to create a new in-memory-datasource, do the following:
var inmemds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"] .createInstance(Components.interfaces.nsIRDFDataSource);
This is necessary since you wouldn't want to use the same in memory data every time.
The RDF service caches datasources that have been loaded. That means that when you try to retrieve the same URI again using either the GetDataSource or GetDataSourceBlocking functions, the RDF service will return the same object. This avoids creating and loading the same datasource repeatedly every time it is referred to. In addition, it means that you can call the RDF service for the same datasource multiple times and get back the same object every time.
This above description isn't technically exactly how the cache works though. When either the GetDataSource or GetDataSourceBlocking function is called, these functions look in the RDF service's cache of already used datasources. If the URI is found in the cache, that datasource is returned. If the URI is not found, a new datasource of the appropriate type is created, as described above. The RDF service does not add the newly created datasource to the cache however. This is the responsibility of the datasource itself, by calling the RDF service's RegisterDataSource method. This method will add the datasource to the RDF service's cache. If the datasource never calls this method, the datasource will never appear in the cache.
This will create a new datasource every time. Other datasources will call RegisterDataSource so a new object is not created each time. When RegisterDataSource is called, the RDF service queries the datasource for it's URI and stores that in the cache. Note that it's possible for a datasource to return a different URI than was used to create it, which may allow for some interesting possibilities. The xml-datasource datasource calls the the RegisterDataSource method, but the URL will be the URL of the RDF/XML file. A datasource may call the RegisterDataSource method at any time, so it's possible to cache a datasource beforehand.
A corresponding UnregisterDataSource method of the RDF service is used to remove datasources from the cache. This will normally be called by the datasource's destructor, that is, when the datasource is deleted. A datasource will be deleted when no references to it exist any more. When it is deleted, the datasource should unregister with the RDF service, and it will be removed from the cache. The next time the URL is asked for, the datasource will need to be created again. While it is possible to remove a datasource from the RDF service's cache at any time by using UnregisterDataSource, the datasource and its data will continue to exist until there are no more references to it.
How Datasources Work in XUL Templates
XUL elements may have a datasource associated with them. Any XUL elements in a XUL document may have a datasource, although its also possible for other types of elements to have datasources if they are placed in a XUL document. Elements that are not in a XUL document cannot use templates. The datasource associated with an element may be retrieved by getting the value of the database property of the element. The database is always a composite-datasource which holds a number of RDF datasources. It can hold any number of datasources and may even hold none, and the set of sources can be modified by using the methods of the nsIRDFCompositeDataSource interface.
Most elements will not have a datasource associated with them so the database property will be null. You can indicate that you would like an element to have a database by adding the datasources attribute to an element. The value of this attribute should be a space-separated list of URIs of the initial datasources to be added to the database. While you can change the datasources by using the methods of the nsIRDFCompositeDataSource interface, the datasources attribute only represents the initial datasources to be used. Changing the value of the attribute does not change the datasources in the database.
For example, in the example below, two datasources are assigned as the initial datasources to be associated with the tree element.
<tree datasources="rdf:bookmarks animals.rdf">
This will cause a composite-datasource to be attached to the tree containing the two datasources specified. The first datasource is rdf:bookmarks which is the datasource Mozilla uses to hold the user's bookmarks. The second datasource is treated as the URI of an XML-RDF file, in this case relative to the XUL file.
In this example, the bookmarks datasource would only be used if the code is privileged, which usually means that it is running in a chrome application. Unprivileged code will not be able to access the bookmarks datasource.
An additional difference between privileged and unprivileged code is that for privileged code, the datasource rdf:local-store is always included in the list of the datasources regardless of whether it was specified or not. This datasource is normally used to hold various bits of state information such as window sizes, which toolbars are showing, tree column sizes, and so forth. This means that the database in the example above will actually have three datasources, the first being the local store, the second is the bookmarks and the third is the animals.rdf file. Unprivileged code running on a remote web site does not include this datasource, since it contains user information.
Once a database has been applied to an element, it is not removed until the document is destroyed, which will happen when the user navigates to another page or closes the window. Thus, removing an element and then adding it to the document again will maintain the same database. If an element with a datasources attribute that has not been in the document before is inserted into the document, a new database will be created for it.
It is normal to use a template inside the element that has a database, although not strictly necessary. The template must either be a template element that is a direct child of the element with the datasources attribute, or referred to using a template attribute. In the latter case, the value of the attribute should be the value of the id attribute on a template element somewhere in the document. This allows a single template to be used in several places, although this usage is not common.
A template builder will be used to construct the actual content from the template. It will use the template to construct new DOM nodes which will be inserted into the document. If the data in a datasource associated with an element changes, the template builder will regenerate the data, adding, removing or changing the content as necessary. The template builder handles this by registering as an observer of the datasource, so that it will be notified of changes.
Automatic rebuilding of the template content occurs only in two situations. The first is when the underlying RDF datasource changes. When the RDF changes, the template builder will be notified of the change. The template builder will rebuild the parts of the content that would be affected by the change, but does not change the parts of the content that would not be affected. This automatic rebuilding occurs if either a Assert, Unassert or Change operation occurs in the datasource, but not on a Move operation. The template builder will also rebuild automatically after a batch operation when the onEndUpdateBatch method is called.
The second situation that will trigger an automatic rebuild of template content is when the ref attribute is changed on the outer element with the database. The will cause the entire content to be rebuilt. Note that the value must be changed to a different value. Setting the ref attribute to the same value does not cause a rebuild. Be careful of this, since earlier Mozilla builds do rebuild even when the value is simply set to the same value.
All other modifications do not trigger a rebuild. Specifically, adding or removing datasources, changing the datasources attribute, or modifying the template contents, do not cause an automatic rebuild. If in doubt, do a manual rebuild.
The rebuild can be performed manually by calling the template builder's rebuild method. All XUL elements have a builder property which will be a reference to the template builder associated with the element. If there is no template builder associated with an element, the value of this property will be null. The following code is used to rebuild the contents from a template, where element is the element with the database:
Changing the ref attribute results in the same underlying code being executed as a manual rebuild, except that the new root node is used instead to build the content.
The template builder will often lazily create content. That means that it will only create the actual content when it is necessary to display it. For instance, the content of a menu is not generated until the menu is first opened. Similarly, child nodes in tree content are not created until the parent node is opened. This is something to watch out for when examining the resulting DOM tree.