Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: disregard Deleted Objects

Fedora Repository Views

DOMS employs an overall atomistic data model. Atomistic data models are much more flexible than traditional compound data models, but they have one big (and largely unmet) challenge. When working with data objects you will frequently need to operate on a number of objects as if they were a common whole. The easiest usecase for this is the public dissemination of data. If the data that should go into one Dissemination Information Package is distributed over several objects, the system needs to understand this. Search indexing is another usecase. Search services tend to use a flat index, each record contain all it's metadata.

...

But the angle one views the repository might also affect the number of entries seen. The above, recursive approach will always lead to one entry per data object. The remedy for this is to mark some classes as Entries for a certain view angle. This means that to compute the records for a given view angle, the view of all objects of a class that is an Entry should be computed. This is the view of the repository.

Fedora Implementation 

This section describes how the above could be implemented in Fedora. 

...

Code Block
languagejava
Set<Object> visitedObjects;
 
List<Object> CalculateView(Object o) {
	List<Objects> view = new List<Objects>();
 
	if (visitedObjects.contain(o){
		return view;
	}


	visitedObjects.add(o);
 	if (o.isDeleted){
		return view;
	}
	ContentModel c = o.getContentModel();
	List<Relation> view-rels = c.getViewRelations();
	List<Relation> object-rels = o.getRelations();
 
	for (Relation r : object-rels){
		if (view-rels.contain(r)){
			view.addAll(CalculateView(r.getObject());
		}
	}
 
	List<Relation> view-invrels = c.getInverseViewRelations();
	List<Relation> object-invrels = o.getInverseRelations();
	for (Relation r : object-invrels){
		if (view-invrels.contain(r)){
			view.addAll(CalculateView(r.getSubject());
		}
	}
 
	return view;
}

...

  • When finding the annotated relations for a given view angle for a data object, get the list from each of the content models, merge it to one list and remove the duplicates. These are the view relations of this object. Do the same with the inverse relations.
  • If a content model marks an object as an Entry for a given view angle, the object is an entry. It does not matter if it has other content models that does not mark it as an entry. An object can of course be an entry for several view angles.   

Update Tracking

This document describes how the backend services should use to view to detect changes in records.

Motivation

Given that we want to work on Views of data, we want to be able to monitor when an object View has changed. We say that an object View has changed if any of the objects in the View have changed.

States in Fedora get special treatment for a view. A View is considered Active if all objects are Active. A View is considered Deleted if the entry object is Deleted. In all other combinations the View is considered Inactive.

We want to be able to return all Views in a given Collection for a given State that have been modified after a given Time. To do this, we maintain a database of Views that is updated on all changes of an object.

Maintaining state

Whenever one of the components of a View is changed, the whole View counts as updated. As such, any services that subscribe to the View in any way need to be notified. If there is a search index for the Views, and one is updated, its state in the index must be recomputed.

The problem arrives when trying to do this. The View system is designed to ease the computing of a View when knowing the Entry object. The reverse is finding the Views, ie. the Entry objects, that have this data object in their View. Rather than encoding this information in the model, we chose to keep an external record of all the views.

The external record will be SQL based, or something similar. It will have two tables.

The first table, ENTRIES, will have these columns

  • entryPid: This is the pid of the entry object
  • viewAngle: This is the viewangle
  • state: This is the fedora object state
  • dateForChange: This is the timestamp when this row was created
  • collectionPid: This is the collection this entry object is part of
  • contentModelPid: This is the content model that marked this object as an entry object in this view angle

EntryPid, viewAngle and State will form an unique key.

To explain the reasoning: Each Entry Object can be an entry object for multiple viewAngles. If the object state is changed, the old entry should remain. As such, each entry object can result in many rows.

The second table, OBJECTS, will have these columns

  • objectPid: The pid of this object
  • entryPid: The pid of the entry object that includes this object
  • viewAngle: The name of the view angle by which the entry object includes this object

Finding Changed objects

To find changed objects we will ask for a set of objects with the following criteria

  • collectionPid
  • state
  • viewAngle
  • offset
  • limit

This can easily be found by a simple query in the ENTRIES table.

 

Changing an object and marking the view as updated

Basically, we need three kinds of operations to handle updates:

  • We need to update the time for when a bundle was last updated. We'll call this "updateTimestamps"
  • We need to update which bundles in which states exist. We'll call this "modifyState"
  • We need to update which objects are part of the view. We'll call this "recalculateView"

There are a fixed number of operations that can be done on objects in doms. 

For each of these, this is what should be done on the index as a result

...

Object Created: The Object was created in DOMS
Fedora operations:
- ingest
Action:

Code Block
  modifystate()
  recalculateview()

 

...

Object Deleted: The Object was purged from DOMS
Fedora operations:
- purgeObject
Action:

Code Block
  modifystate('D')

 

...

Object State Changed: The Object changed state in DOMS
Fedora operations:
- modifyObject
Action:

Code Block
  modifystate()

 

...

Datastream Changed: The Object datastreams changed. Handled differently depending on whether this is the relations datastream
Fedora operations:
addDatastream
- modifyDatastreamByReference
- modifyDatastreamByValue
purgeDatastream
- setDatastreamState
- setDatastreamVersionable
updatetimestamp
Action:

Code Block
  if RELS-EXT
    recalculateview()
  else
    updatetimestamp()

 

Object Relations Changed: The Object changed in a fashion that DOES require the view to be recomputed.
Fedora operations:
- addRelationship
- purgeRelationship
Action:

Code Block
  recalculateview()

Each of these operations will be elaborated below

Modifystate

When the object state changes, we will have to update the state in the object, and possibly in any view containing the state.

So for each ENTRIES object containing this object, we update it with a new timestamp and state

  • Active, if the new state is Active and all other objects in the view are current Active
  • Deleted, if this is the entry object, and
  • Inactive, otherwise
Code Block
languagejava
modifystate(pid, date, state) {
    //If this object was previously unknown and is an entry object, add it as a new entry object
    if (!OBJECTS.contains(pid)) {
        List<viewangle,cmpid,collection> viewEntries = doms.getViewEntries();
        foreach (viewEntry : viewEntries) {
            ENTRIES.add(pid,date,state,viewangle,cmpid,collection)
            OBJECTS.add(pid,pid,viewangle)
        }
    }
 
	//Find the DomsObject rows that regard this object.
	//There will be one per entry/viewAngle combination
	results = OBJECTS.list(objectPid=pid);

	//Find all Entries that include this object
	foreach (result : results) {
        oldstate = result.entryPid.state;
        newstate = calculatestate(result.entryPid, pid, timestamp, state) //TODO! Missing        
        // If this deletes the entry, handle that
        if (newstate = 'D') {
            if (oldstate != 'D') {
              ENTRIES.removeAll(result.entryPid)
              OBJECTS.removeAll(entryPid=result.entryPid)
              ENTRIES.add(entryPid,'D',timestamp)
            }
            return
        }
        // If it is set active, remove any deleted entries
        if (newstate = 'A') {
            ENTRIES.remove(result.entryPid, 'D');
     }
     updatetimestamp()

Updatetimestamp

Code Block
// Update the Entries table regarding a change
void updatetimestamps(String pid, Date date) {
	objects = OBJECTS.list(objectPid=pid);

	//Find all Entries that include this object
	foreach (object : objects) {

        state = calculatestate(object.entryPid) //TODO! Missing
 
        //If entry is currently deleted, skip
        if (state = 'D') {
            return;
        }
        //Find the Entry objects that fulfill these restrictions
        List<Entry> results = ENTRIES.list(entrypid = object.entrypid)

        for (Entry result : results) {
            //Only update active entry if bundle is active
            if (result.state = 'A' and state != 'A') {
                continue
            }
	        //Is this entry older than the current change?
            if (result.getDateForChange < date) {
                ENTRIES.update(result, date);
            }
        }
    }
} 

Recalculateview

An object's relations changed. This could change which objects are in which entry's views. 

We find all the Entries that contain this object by listing OBJECTS.

For each of these, we recalculate the view bundle and update the relevant rows in OBJECTS and ENTRIES.

Each row in OBJECTS specify that an object is contained in a named view for a specific entry object.

Then we update the ENTRIES table to mark that the view is changed. 

This may also update content model or collection relations, which is handled separately

...