Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: fixed some bad formatting from markdown2confluence

...

Sakai Conditional Release Developer's Guide

Motivation

Design

The first design attempt produced a plan for a system that would encapsulate into a Excerpt

hiddentrue
Rule Excerpt
hiddentrue
arbitrarily many queries to arbitrarily many Sakai services. In this way, if a particular resource were protected by a Excerpt
hiddentrue
Rule Excerpt
hiddentrue
, accessing the resource could cause a request to the Excerpt
hiddentrue
GradebookService Excerpt
hiddentrue
, another request to Excerpt
hiddentrue
ContentHostingService Excerpt
hiddentrue
, and so on. This design was deemed too chatty for reasonable performance in the Resources tool, which must conform to many of the performance characteristics of a file system.

...

Sakai has a built-in mechanism for responding to events, called the Excerpt

hiddentrue
NotificationService Excerpt
hiddentrue
. Clients of the service ask it to create objects of type Excerpt
hiddentrue
Notification Excerpt
hiddentrue
. The Excerpt
hiddentrue
Notification Excerpt
hiddentrue
extends Sakai Excerpt
hiddentrue
Entity Excerpt
hiddentrue
and specifies three additional key pieces of information:

Excerpt
hiddentrue


one or more strings containing event types to respond to. These are called functions.
a string called a resource filter which indicates which resource may be affected by the event.
an object of type Excerpt
hiddentrue
NotificationAction Excerpt
hiddentrue
which has the Excerpt
hiddentrue
notify() Excerpt
hiddentrue
method for taking action in response to the event.
Excerpt
hiddentrue

The conditions module contains an implementation of Excerpt

hiddentrue
NotificationAction Excerpt
hiddentrue
called Excerpt
hiddentrue
ResourceReleaseRule Excerpt
hiddentrue
, which also implements Excerpt
hiddentrue
Predicate Excerpt
hiddentrue
from Apache commons-collections. The purpose of Excerpt
hiddentrue
Predicate Excerpt
hiddentrue
is to add a
Excerpt
hiddentrue

boolean evaluate(Object arg)
Excerpt
hiddentrue

method which answers the question "Has the condition been met?" The Excerpt
hiddentrue
notify() Excerpt
hiddentrue
method checks the condition and then takes the necessary action to update the state of the Excerpt
hiddentrue
ContentHostingService Excerpt
hiddentrue
.

Walkthrough

We now have all the pieces to describe exactly how conditional release works. Since the application of conditions is asynchronous, there can be an arbitrary space of time between steps 2 and 3:

Excerpt
hiddentrue


Instructor edits details on an item in the Resources tool and is presented with "Only if the following conditions is met" option. The list of available assignments and their conditions is passed from the Excerpt
hiddentrue
ConditionService Excerpt
hiddentrue
with no direct dependency from the Resources tool and the Excerpt
hiddentrue
GradebookService Excerpt
hiddentrue
.
Instructor saves the details, and within Excerpt
hiddentrue
ResourcesAction Excerpt
hiddentrue
a Excerpt
hiddentrue
Predicate Excerpt
hiddentrue
is created from the condition parameters and is added to a fresh instance of Excerpt
hiddentrue
ResourceReleaseRule Excerpt
hiddentrue
which in turn is added to a fresh instance of Excerpt
hiddentrue
Notification Excerpt
hiddentrue
, along with the event type (function) to respond to and the resource filter (the resource's ID). The Excerpt
hiddentrue
NotificationService Excerpt
hiddentrue
takes care of persisting the Excerpt
hiddentrue
Notification Excerpt
hiddentrue
.
As events propagate through Sakai, Excerpt
hiddentrue
BaseNotificationService Excerpt
hiddentrue
examines each one for a matching function in its stored list of Excerpt
hiddentrue
Notifications Excerpt
hiddentrue
. If a stored Excerpt
hiddentrue
Notification Excerpt
hiddentrue
matches both the function and resource filter of the inbound event, its embedded Excerpt
hiddentrue
NotificationAction Excerpt
hiddentrue
is dereferenced and its Excerpt
hiddentrue
notify() Excerpt
hiddentrue
method is called.
In the case of a Excerpt
hiddentrue
ResourceReleaseRule Excerpt
hiddentrue
, an argument necessary for evaluating the embedded Excerpt
hiddentrue
Predicate Excerpt
hiddentrue
will be pulled from the event and the embedded Excerpt
hiddentrue
Predicate Excerpt
hiddentrue
will be evaluated. In response to the evaluation, the access rules of the underlying content resource will either be modified or left alone.
Excerpt
hiddentrue

...

...

BooleanExpression

...

The Excerpt

hiddentrue
Predicate Excerpt
hiddentrue
within the Excerpt
hiddentrue
ResourceReleaseRule Excerpt
hiddentrue
is delegated to another class called Excerpt
hiddentrue
BooleanExpression Excerpt
hiddentrue
. The Excerpt
hiddentrue
BooleanExpression Excerpt
hiddentrue
is how we programmatically encapsulate such English language questions as "Is the assignment score above 80?" We have a conventional boolean expression here, with a left term, an operator, and a right term. What makes this interesting is that at the time the instructor sets up the condition, a necessary piece of information to answer the question is missing. In our example, the missing piece is the left term, the assignment score in question. This data only materializes later, at the time the instructor submits one or more assignment grades.

At the time a Excerpt

hiddentrue
BooleanExpression Excerpt
hiddentrue
is evaluated, a data object is passed to it as an argument. This data object contains the missing data to fulfill the expression. At
Excerpt
hiddentrue

<em>

construction
Excerpt
hiddentrue

</em>

time, the Excerpt
hiddentrue
BooleanExpression Excerpt
hiddentrue
is supplied with the fully qualified name of the class it can expect to receive as an argument when it is evaluated, the name of a method to call on that object to retrieve the missing left term, the operator to use in the evaluation, and the value of the right-hand term of the expression.

For our example, the condition that "assignment score must be above 80" is saved in

Excerpt
hiddentrue

ResourcesAction.saveCondition()
Excerpt
hiddentrue

by creating a new Excerpt
hiddentrue
BooleanExpression Excerpt
hiddentrue
with these arguments:

Code Block
borderStylesolid
eventDataClassName: "org.sakaiproject.conditions.impl.AssignmentGrading"
missingTermMethodName: "getScore"
operator: "greater_than"
argument: new Double(80)

When the grading event occurs, the Excerpt

hiddentrue
ResourceReleaseRule Excerpt
hiddentrue
creates a simple object representation of the grading event in an Excerpt
hiddentrue
AssignmentGrading Excerpt
hiddentrue
object, which becomes the argument passed to the Excerpt
hiddentrue
BooleanExpression Excerpt
hiddentrue
's
Excerpt
hiddentrue

evaluate(Object arg)
Excerpt
hiddentrue

method. The Excerpt
hiddentrue
BooleanExpression Excerpt
hiddentrue
uses reflection to invoke the Excerpt
hiddentrue
getScore() Excerpt
hiddentrue
method on the Excerpt
hiddentrue
AssignmentGrading Excerpt
hiddentrue
object. It then has everything it needs to evaluate the expression:
Excerpt
hiddentrue

((AssignmentGrading)arg).getScore() > 80
Excerpt
hiddentrue

Note that we also have conditions that don't require an operator or a missing term, such as "Has the assignment due date passed?" In this case, the arguments to construct a Excerpt

hiddentrue
BooleanExpression Excerpt
hiddentrue
are as follows:

Code Block
borderStylesolid
eventDataClassName: "org.sakaiproject.conditions.impl.AssignmentUpdate"
missingTermMethodName: "dueDateHasPassed"
operator: "no_operator"
argument: null

...

Events are useful for getting information as it becomes available, but do not help you if you want to evaluate the state of the system long after the event has fired. For example, when an instructor first sets up a condition for a particular grade in a particular assignment, some students might already have a grade at that point. If we are waiting for an event to evaluate that student's access to the resource, the event will never come. Our solution to this is to produce our own "new condition" event that must then pull data from the Excerpt

hiddentrue
GradebookService Excerpt
hiddentrue
. This works, but is a little awkward and depends on the service API to expose the information we want.

Not all the events you might want to respond to are in Sakai already. For our baseline of functionality, we needed to create a new event in the Excerpt

hiddentrue
GradebookService Excerpt
hiddentrue
for individual assignments as they are graded, and another event when grades are submitted from an external tool such as Samigo or Assignments.

The Excerpt

hiddentrue
ConditionService Excerpt
hiddentrue
does not yet fulfill its true purpose, which is to publish a list of conditions from any Sakai service that registers with it. We did not need this capability for the functionality of our first iteration, so the Excerpt
hiddentrue
ConditionService Excerpt
hiddentrue
is only sending a static Excerpt
hiddentrue
Map Excerpt
hiddentrue
of strings for the Resources tool to embed directly into its template markup. To do this the "right way" is to devise a tree-shaped object graph for conditions, where each node is a criterion, and each path from the root of the tree to a leaf represents one completely-defined condition. For an example of an implementation in another system, see Apple's NSRuleEditor, part of their Cocoa application framework.

The hard part of implementing the true Excerpthidden true ConditionService Excerpt

hiddentrue
is that you must translate the object graph of conditions into a JavaScript object so that the HTML form in your user interface can be dynamic. Furthermore, we must be able to implement the form in the tool author's view technology of choice (Velocity, JSF, RSF, etc.).

Sakai's Excerpt

hiddentrue
BaseNotificationService Excerpt
hiddentrue
has its limitations, notably that it requires any implementations of Excerpt
hiddentrue
NotificationAction Excerpt
hiddentrue
to be physically inside its component Excerpt
hiddentrue
{{WEB-INF Excerpt
hiddentrue
}}directory. Also, it uses manual manipulation of DOM objects for persistence, which is error-prone and yields object storage in the database as a lump of XML.