...
Sakai Conditional Release
...
Developer's Guide
Design
The first design attempt produced a plan for a system that would encapsulate into a Excerpt |
---|
|
Rule
Excerpt |
---|
|
arbitrarily many queries to arbitrarily many Sakai services. In this way, if a particular resource were protected by a
Excerpt |
---|
|
Rule
Excerpt |
---|
|
, accessing the resource could cause a request to the
Excerpt |
---|
|
GradebookService
Excerpt |
---|
|
, another request to
Excerpt |
---|
|
ContentHostingService
Excerpt |
---|
|
, 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.
...
For example, we want to be able to restrict access to a particular folder to just those students who score above an 80 on a recent quiz. When the instructor grades her students, each assignment grade goes out into Sakai as a Excerpt |
---|
|
gradebook.updateItemScore
Excerpt |
---|
|
event. The folder in question has an event-listener registered in its name. When all the events go out, the event listener examines each one for the specified condition (grade must be above 80). When a particular event (student's grade) meets the condition, that student's ID is added to the ACL for the folder under protection, and thereafter the student will be able to access it.
Sakai has a built-in mechanism for responding to events, called the Excerpt |
---|
|
NotificationService
Excerpt |
---|
|
. Clients of the service ask it to create objects of type
Excerpt |
---|
|
Notification
Excerpt |
---|
|
. The
Excerpt |
---|
|
Notification
Excerpt |
---|
|
extends Sakai
Excerpt |
---|
|
Entity
Excerpt |
---|
|
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
...
NotificationAction
...
- which has the
...
notify()
...
- method for taking action in response to the event.
...
true | The conditions module contains an implementation of
Excerpt |
---|
|
NotificationAction
Excerpt |
---|
|
called
Excerpt |
---|
|
ResourceReleaseRule
Excerpt |
---|
|
, which also implements
Excerpt |
---|
|
Predicate
Excerpt |
---|
|
from Apache commons-collections. The purpose of
Excerpt |
---|
|
Predicate
Excerpt |
---|
|
is to add a
Excerpt |
---|
|
boolean evaluate(Object arg)
Excerpt |
---|
|
method which answers the question "Has the condition been met?" The
Excerpt |
---|
|
notify()
Excerpt |
---|
|
method checks the condition and then takes the necessary action to update the state of the
Excerpt |
---|
|
ContentHostingService
Excerpt |
---|
|
.
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 |
hidden
true | - 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
...
ConditionService
...
- with no direct dependency from the Resources tool and the
...
GradebookService
...
- .
- Instructor saves the details, and within
...
ResourcesAction
...
- a
...
Predicate
...
- is created from the condition parameters and is added to a fresh instance of
...
ResourceReleaseRule
...
- which in turn is added to a fresh instance of
...
Notification
...
- , along with the event type (function) to respond to and the resource filter (the resource's ID). The
...
NotificationService
...
- takes care of persisting the
...
Notification
...
- .
- As events propagate through Sakai,
...
BaseNotificationService
...
- examines each one for a matching function in its stored list of
...
Notifications
...
- . If a stored
...
Notification
...
- matches both the function and resource filter of the inbound event, its embedded
...
NotificationAction
...
- is dereferenced and its
...
notify()
...
- method is called.
- In the case of a
...
ResourceReleaseRule
...
- , an argument necessary for evaluating the embedded
...
Predicate
...
- will be pulled from the event and the embedded
...
Predicate
...
- will be evaluated. In response to the evaluation, the access rules of the underlying content resource will either be modified or left alone.
...
...
BooleanExpression
...
...
The Excerpt |
---|
|
Predicate
Excerpt |
---|
|
within the
Excerpt |
---|
|
ResourceReleaseRule
Excerpt |
---|
|
is delegated to another class called
Excerpt |
---|
|
BooleanExpression
Excerpt |
---|
|
. The
Excerpt |
---|
|
BooleanExpression
Excerpt |
---|
|
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 |
---|
|
BooleanExpression
Excerpt |
---|
|
is evaluated, a data object is passed to it as an argument. This data object contains the missing data to fulfill the expression. At
construction
time, the
Excerpt |
---|
|
BooleanExpression
Excerpt |
---|
|
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
ResourcesAction.saveCondition()
by creating a new
Excerpt |
---|
|
BooleanExpression
Excerpt |
---|
|
with these arguments:
Code Block |
---|
|
eventDataClassName: "org.sakaiproject.conditions.impl.AssignmentGrading"
missingTermMethodName: "getScore"
operator: "greater_than"
argument: new Double(80)
|
When the grading event occurs, the Excerpt |
---|
|
ResourceReleaseRule
Excerpt |
---|
|
creates a simple object representation of the grading event in an
Excerpt |
---|
|
AssignmentGrading
Excerpt |
---|
|
object, which becomes the argument passed to the
Excerpt |
---|
|
BooleanExpression
Excerpt |
---|
|
's
evaluate(Object arg)
method. The
Excerpt |
---|
|
BooleanExpression
Excerpt |
---|
|
uses reflection to invoke the
Excerpt |
---|
|
getScore()
Excerpt |
---|
|
method on the
Excerpt |
---|
|
AssignmentGrading
Excerpt |
---|
|
object. It then has everything it needs to evaluate the expression:
((AssignmentGrading)arg).getScore() > 80
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 |
---|
|
BooleanExpression
Excerpt |
---|
|
are as follows:
Code Block |
---|
|
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 |
---|
|
GradebookService
Excerpt |
---|
|
. 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 |
---|
|
GradebookService
Excerpt |
---|
|
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 |
---|
|
ConditionService
Excerpt |
---|
|
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 |
---|
|
ConditionService
Excerpt |
---|
|
is only sending a static
Excerpt |
---|
|
Map
Excerpt |
---|
|
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 Excerpt |
---|
hidden | true ConditionService
Excerpt |
---|
|
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 |
---|
|
BaseNotificationService
Excerpt |
---|
|
has its limitations, notably that it requires any implementations of
Excerpt |
---|
|
NotificationAction
Excerpt |
---|
|
to be physically inside its component
Excerpt |
---|
|
WEB-INF
Excerpt |
---|
|
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
.TO-DO
Here are a few less glamorous things that need doing. These are some suggestions that any volunteer developer could tackle.
- event pack should not have to declare a dependency on conditions-impl. This is in there now in order to avoid a
ClassdefNotFound
error when we ask the NotificationService
to reconstitute a ResourceReleaseRule
from the database. It should be able to get a Class
object from the Sakai ComponentManager
instead. Look in BaseNotificationService
for where it does this: Code Block |
---|
m_action = (NotificationAction) Class.forName(className).newInstance();
|
GradebookExternalAssessmentServiceImpl
should not use a static cover of the EventTrackingService
. This is a case of doing the quick thing rather than the right thing. There's a Spring bean configuration file in there. We just need to modify it to add an injected EventTrackingService
.- The behavior of the content tool UI should not depend on the index number of items in the
<select>
tags. There are hard-coded references to magic indexes in ResourcesAction
, ListItem
, and sakai_properties_scripts.vm
. It's ugly. - Speaking of ugly, the UI does not degrade gracefully if any parts of the form become very long. The box for adding a point value becomes orphaned on the next line. Someone with HTML chops could make it nice.