Modifying RDF Datasources

This section will describe how to add, change and remove information from an RDF datasource.

Datasource Modification

Datasources have four methods which may be used to modify a datasource. It is possible to modify a datasource using only these four methods. Not all datasources are modifiable. These datasources will throw an exception when you attempt to call the methods. For instance, RDF/XML files loaded from a remote site cannot be modified directly.

To add an RDF triple to a datasource, use the Assert method. Given a subject, a predicate and a target, this method will add that statement to the datasource. If the statement already exists, it is added again. That means that you may wish to use the GetTarget method to check for the statement first. In Mozilla terminology, an RDF triple or statement is called an assertion, which is where the method gets it name from. Essentially, we are asserting some information.

Let's say that we wanted to add a name for a person. Here is an example of how to do that.

var subject = rdfService.GetResource("http://www.xulplanet.com/rdf/people/David");
var predicate = rdfService.GetResource("http://www.xulplanet.com/rdf/people/name");
var name = rdfService.GetLiteral("David");
datasource.Assert(subject, predicate, name, true);

First, we get resource objects for the subject and the predicate. We want to add a name, so we use the 'name' predicate, qualified with the namespace. The name itself is a literal so we get a literal object for this. All three are retrieved from the RDF service. We could have used GetAnonymousResource to get the subject if we wanted to ensure that the resource URI was unique.

Finally, Assert is called which takes four arguments, the subject, the predicate, the target and the truth value. This last argument can be used to specify that something is false. This is rarely useful, so the fourth argument should almost always be true.

Datasources may not accept the change. For instance, when a datasource is not writeable or you attempt to add invalid data, the datasource will reject the change. In native code, you can detect this as the datasource will return an NS_RDF_ASSERTION_REJECTED status code. This status code in not an error however.

To remove a statement from a datasource, there is a similar Unassert method. This method will remove a single statement matching the subject, predicate and target arguments. For example, we could remove the earlier statement we just added with the following:

datasource.Unassert(subject, predicate, name);

It doesn't matter whether the statement exists on not. The Unassert method will not cause an error if it doesn't exist.

The Change method may be used to change the target of a statement to another value. For example, this might be used to change a person's name to another value. This is equivalent to removing the old value by calling Unassert and then adding the new value by calling Assert. However, the Change method does both in one step.

Here is an example:

var subject = rdfService.GetResource("http://www.xulplanet.com/rdf/people/George");
var predicate = rdfService.GetResource("http://www.xulplanet.com/rdf/people/name");
var oldName = rdfService.GetLiteral("George");
var newName = rdfService.GetLiteral("Georgina");
datasource.Change(subject, predicate, oldName, newName);

In this case, we change the value of the name predicate for the 'George' resource from the value 'George' to 'Georgina'. The old value will be removed from the datasource and replaced with the new value.

Finally, the Move method may be used to change the source of a statement to another value. This would be used to change the resource that has a given name. For example, that we might decide that Georgina's name should actually be associated with a 'Georgina' resource.

var oldSubject = rdfService.GetResource("http://www.xulplanet.com/rdf/people/George");
var newSubject = rdfService.GetResource("http://www.xulplanet.com/rdf/people/Georgina");
var predicate = rdfService.GetResource("http://www.xulplanet.com/rdf/people/name");
var name= rdfService.GetLiteral("Georgina");
datasource.Move(oldSubject, newSubject, predicate, name);

As with the Change method, the old value is removed and the new value is added. The Move method is the reverse in that it changes the sources instead of the targets.

Both the Change method and the Move method don't stop if the old value was not present. For both methods, they will still add the new value. Because of this, you should ensure that the old value exists first if this is a concern.

Modification Example

Let's say you are building a catalog of photos. You might have a variety of pieces of information to store for each photo, such as the location, the date and a description of the photo. For each photo we will have a resource and a number of properties. The properties don't have to be the same for each photo, so we can leave things out for some photos and add more detail for others. A user interface displaying the data might only show the fields that are present.

First, let's start off by creating a new empty in-memory datasource:

var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].
                   getService(Components.interfaces.nsIRDFService);
var photosDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                 .createInstance(Components.interfaces.nsIRDFDataSource);

Next, let's add some data for each photo. We could use GetAnonymousResource to generate anonymous resource URIs for each photo. Or, we could use the URI of where the photo is placed on a web site. We could use either since RDF uses the URI only as a placeholder and doesn't download the content. In our example, let's assume example URIs such as http://www.example.com/image/clown.jpeg.

The first photo has two properties, a date and a description. We will need to make two calls to the Assert method to add this information. For example:

var photoRes = rdfService.GetResource("http://www.example.com/image/clown.jpeg");
var dateProp = rdfService.GetResource("http://www.example.com/rdfns/date");
var descriptionProp = rdfService.GetResource("http://www.example.com/rdfns/description");
var photoDate = rdfService.GetLiteral("January 20, 2003");
var photoDescription = rdfService.GetLiteral(
      "The clown at Simone's birthday party makes a funny face.");
photosDS.Assert(photoRes, dateProp, photoDate, true);
photosDS.Assert(photoRes, descriptionProp, photoDescription, true);

First, we get the resource and literal objects needed. We could have used an nsIRDFDate instead of a plain literal for the date, but it doesn't matter for this example.

The two Assert lines add the date and the description respectively. If we later decided that we wanted to remove a property, we can just use the Unassert method.

Let's add a second photo, in this case one taken on the same date.

var photo2Res = rdfService.GetResource("http://www.example.com/image/cake.jpeg");
var photo2Description = rdfService.GetLiteral(
      "Simone blows out the candles on her cake to officially mark that she is four.");
photosDS.Assert(photo2Res, dateProp, photoDate, true);
photosDS.Assert(photo2Res, descriptionProp, photo2Description, true);

In this case, we don't need to get as many resource and literal objects since we already have the objects from before. There is no point asking the RDF service for these objects repeatedly since the RDF service will return the same object anyway. The date is the same also so we can just reuse the object. The Assert method is called twice as before, but using the data for the second photo.

Since both dates are the same, we can query the datasource for all the photos on that date by using the GetSources method. The example code below will return an enumeration with two items in it, since there are two matching photos for the date 'January 20, 2003'. Note that the string must be an exact match.

var sources = photosDS.GetSources(dateProp, photoDate, true);
while (sources.hasMoreElements()){
  var photoRes = sources.getNext();
  if (photoRes instanceof Components.interfaces.nsIRDFResource){
    var description = photosDS.GetTarget(photoRes, descriptionProp, true);
    if (description instanceof Components.interfaces.nsIRDFLiteral){
      alert(description.Value);
    }
  }
}

For each item in the enumeration, we call the GetTarget method to get the description for the photo. Given only the date of the photo, we will have found the two descriptions. These kinds of queries allow one to navigate through the information in the RDF graph easily.

Next, we decide to change the description of the first photo. We don't want to create a new resource and add the data again. We will instead just use the Change method to achieve this. This method will allow the value of a property to be changed from one value to another. We cannot just call Assert since that will add a second value, and not remove the other one. This might be useful in some cases however. Here is an example of changing a value.

var photoNewDescription = rdfService.GetLiteral(
      "Simone laughs when the clown at her birthday party makes a funny face.");
photosDS.Change(photoRes, descriptionProp, photoDescription, photoNewDescription);

Sometime later, we might find out that the descriptions of the photos are reversed. We could use the Move method to change them so that the descriptions are associated with the right resources. It is also possible to unassert the old values and reassert the values in the right place.

photosDS.Move(photoRes, photo2Res, descriptionProp, photoNewDescription);
photosDS.Move(photo2Res, photoRes, descriptionProp, photo2Description);

The first statement adjusts the first description from the first photo resource to the second resource. The second statement adjusts the second description from the second photo to the first. This is a rather trivial example, but certainly useful. A more effective example of the use of the Move method is when moving a resource from one place to another. For example, if photos are stored in a set of groups, one could move a photo from one group to another by using the Move method to change the group it was associated with. For example:

var oldFolderRes = rdfService.GetResource("http://www.example.com/folder/unsorted");
var newFolderRes = rdfService.GetResource("http://www.example.com/folder/simonesbirthday");
var photoChildProp = rdfService.GetResource("http://www.example.com/rdfns/photo");
photosDS.Move(oldFolderRes, newFolderRes, photoChildProp, photoRes);

This example will move a photo from one group to another. We could use similar code to move a group inside another group. In this case we are using a property to indicate that a photo is in a group. We might want to use an RDF container instead. This is described in the next section.

Observing Datasource Changes

RDF datasources may have one or move observers attached to them. The observers are called whenever the datasource changes. This can be useful to listen to the datasources provided by Mozilla or to your own datasources. For instance, this might help to keep certain code separate from each other, if desired. The XUL template builder uses observers to listen to changes to the RDF datasource so that the template contents can be rebuilt.

You can add an observer to a datasource with the AddObserver method of a datasource. You can remove it again using the RemoveObserver method. Datasources may have several observers and they will all be called when the datasource changes. Observers should implement methods of the nsIRDFObserver interface.

The observer receives a separate notification for each modification to the datasource. Arguments supplied to the observer's methods indicate what changed. The observer should implement a method for each of the four types of change that can occur in datasource, as described above. Those changes are assertion, unassertion, change and move. The nsIRDFObserver has four methods corresponding to these four types, prefixed with 'on'. For example, the onAssert method will be called whenever an Assert call is made on the datasource.

Two additional methods, onBeginUpdateBatch and onEndUpdateBatch are called when the corresponding methods of the datasource are called. Although there is nothing special that these methods need to do, they are an indicator to the datasource and its observers that a large number of changes are going to be made to the datasource. Since it might be inefficient to handle every change, the batching methods allow you to detect when a group of changes starts and ends, and optimize the code for this case. For instance, when a batch operation begins, the XUL template builder doesn't rebuild any template content until the batch operation ends. If the changes were made without batching them, the builder would rebuild on every change which would be inefficient.

You need to implement the six methods of the nsIRDFObserver interface, although you do not need to take any action in all of them. Here is an example:

var observer = {
  onAssert            : function(ds, source, predicate, target)
  {
    var dateProp = rdfService.GetResource("http://www.example.com/rdfns/date");
    var photoDate = rdfService.GetLiteral("January 20, 2003");
    if ((dateProp == predicate) && (photoDate == target)){
      alert("That is Simone's birthday!");
    }
  },
  onUnassert          : function(ds, source, predicate, target){},
  onChange            : function(ds, source, predicate, oldTarget, newTarget){},
  onMove              : function(ds, oldSource, newSource, predicate, target){},
  onBeginUpdateBatch  : function(ds){},
  onEndUpdateBatch    : function(ds){}
};
photosDS.AddObserver(observer);

In this example, we only want to listen to the statements being added to the datasource, so we don't do anything in the other methods. We still need to declare them or errors will occur. The arguments to the onAssert method indicate the data that was added. This is used to compare the date to a specific date and display an alert box if the date in 'January 20, 2003'. Notice that we can compare resources and literals using the == operator.

The in-memory-datasource implements the nsIRDFPropagatableDataSource interface. It has a single property propagateChanges which can be set to true or false. By default, the value of this property is true, but if you change it to false, the observer notification will be disabled. This will disable all observers notifications so they will not be called. Setting the value to true again will reenable the notifications. Changes made while the value is false will not reach the observers. As an example, the bookmarks datasource disables the notifications when sorting a folder, since a large amount of data is shuffled around during the sort operation.

Add a note User Contributed Notes
No comments available

Copyright © 1999 - 2005 XULPlanet.com