Persevere uses a storage structure that closely follows the semantics of JavaScript’s class system. Persevere stores objects in tables that each have a corresponding class/constructor and schema of properties. Every object in a Persevere table is an instance of the table’s class. This provides a very natural clean integration between Persevere’s persisted data and JavaScript environment, and an intuitive class-based object model for building applications. We can see this relationship by creating some classes and interacting with it at in the Persevere Server-Side JavaScript (SSJS) environment.

We will start by creating a new table/class. This can be done from the Persevere browser/explorer by clicking the new button, choosing a class name and saving it. We can also create a new class at the Persevere console. The Persevere console provides direct access to the JavaScript environment and should be available when you directly start Persevere from the command line (java -jar start.jar). From the console we can create a new table/class:

var Project = new Class({id:"Project"});

(It should be noted that we don’t actually need to assign the new class to a local variable, when a class is created it is automatically added to the global scope. By creating a class with an id of “Project”, we can now access the class directly by the global Project variable.)

We now have a new table/class in Persevere and we can now use this class just as we would a constructor in JavaScript and generated instances will be automatically persisted:

// create an instance
var myProject = new Project();
// give it a property
myProject.foo="bar";

We now have a single object in the Project table. We can now query for all the objects in this table from the REST interface:

GET /Project/

Or we can query from the SSJS console:

var allProjects = load("Project/");

This will return an array of all the objects in this class. We can also directly load the newly created instance (if this is a brand new table the first object will have an autogenerated id of 1):

var myProject = load("Project/1");
// the loaded object will still be an instance of Project
myProject instanceof Project // -> true

Object Model

We can do more than just store data in Persevere; we can also add methods to our class that are accessible from all instances. We add methods to classes just as we normally do in JavaScript, by setting functions on the class’s prototype object (you may want to switch into multiline mode, run help at the command line to find out how):

Project.prototype.addTask = function(task){
  if(!this.tasks){
    this.tasks = [];
  }
  this.tasks.push(task);
}

This method will create a new function on the Project prototype called addTask. Persevere is a persistent object environment, so this addition to the class is automatically persisted. When Persevere is restarted, this prototype function will still be available for the Project class. This new method provides the functionality of adding a tasks property with an array to the target instance if it does not exist, and then adding a new task to the tasks array. Anything that can be done from the console can also be done from the RESTful HTTP interface. For example, we could do a PUT to /Class/Project.prototype.addTask with the function above to add this method remotely. We can also directly edit the config files that store the Class structure. By default, new classes are stored in /WEB-INF/config/generated.js, but you can create your own .js or .json files in the /WEB-INF/config directory.

We can create another method to calculate the total cost of the project:

Project.prototype.getTotalCost = function(){
   var total = 0;
   if(this.tasks){
      this.tasks.forEach(function(task){
         total += task.price;
      });
   }
   return total;
}

Note that we can safely use the forEach method to iterate over an array in Persevere because it runs in a JavaScript 1.7 environment (Rhino 1.7). Now we can try out our new methods on an instance of the Project class:

myProject.addTask({name:"create class", price:10});
myProject.addTask({name:"create instance", price: 5});

And once again all these changes to the object referred by myProject will be persisted. In this case an array with two objects will be stored in the Persevere database. And now we will find the total cost:

myProject.getTotalCost() // -> 15

And all methods are available for execution from the browser as well (assuming the user has execute permission for the target object) via JSON-RPC. We can execute the method remotely by using a URL corresponding to the target object and POST message body in JSON-RPC format to define the method and parameters:

POST /Project/1
{
   "method":"getTotalCost",
   "params":[],
   "id":"call1"
}

Inheritance

With Persevere, you can also create subclasses using JavaScript’s prototype-based inheritance and corresponding to tables that extend other tables. When you create a subclass S of class C, that means that instances of S will inherit the same methods (unless overridden) that are available for instances of class C, and all instances of S will also be instances of C. All objects in table S will also exist table C. This also means that instances of S will have a delegate (AKA __proto__ in SpiderMonkey or [[Proto]] in ES3 prose) that is S.prototype, which will have a delegate C.prototype, consistently following the JavaScript prototype-based inheritance pattern.

This may be easier to understand with a simple example. Let’s create another class, TaxedProject, that will be a subclass of Project and include a special method to handle taxes on the costs of tasks:

new Class({"id":"TaxedProject", "extends":Project});

This newly created TaxedProject class is now a subclass of Project, and inherits all the attributes and methods from Project. We can create a new TaxedProject and operate on it just as we would with a Project. We can also create new methods, including methods that override methods from the super class. Here we will create a getTotalTaxes method that calculates the taxes for the project:

TaxedProject.prototype.getTotalTaxes = function(){
   return this.getTotalCost() * this.taxRate;
}

Now we can create a TaxedProject instance, using it like a normal Project, but we can get the taxes information:

var myTaxedProject = new TaxedProject();
myTaxedProject.addTask({name:"create subclass", price: 10});
myTaxedProject.addTask({name:"create instance", price: 5});
myTaxedProject.taxRate = 0.12;
myTaxedProject.getTotalCost() // -> 15
myTaxedProject.getTotalTaxes() // -> 1.8

Now that we have created one new instance of a TaxedProject, we can do a query to get a list of all the instances of TaxedProject, or in other words, all the objects in the TaxedProject table, and we should get one object:

// this will return an array of the objects in TaxedProject
var taxedProjects = load("TaxedProject");
taxedProjects.length // -> 1

Alternately, we can get this same array of the persisted instances of the class with the data property on the class:

// this will return an array of the objects in TaxedProject
var taxedProjects = TaxedProject.data;
taxedProjects.length // -> 1

Since TaxedProject is a subclass of Project, all instances of TaxedProject are also instances of Project. Since we created a direct instance of Project earlier, and an instance of TaxedProject, we now have two instances of Project, or in other words we will have two objects in the Project table. We can query to verify this:

// this will return an array of the objects in Project
var allProjects = Project.data;
allProjects.length // -> 2

We can also use JSONQuery expressions to query these tables, and we can choose to query for all Projects or only TaxedProjects:

// this will return an array of the objects in Project
var projectsWithTwoTasksOrMore = load("Project/[?tasks.length>1]");

Static Methods

With JavaScript we can create virtually any property on any object, and with Persevere we can persist virtually any property, including extra properties on classes. Adding methods directly to a class (instead of its prototype) provides a very natural and organized way to create “static” methods. As an example, let’s create a method that will find the total cost of all the projects that have an active flag set to true:

Project.getCostOfActiveProjects = function(){
   var total = 0;
   // iterate through each of the projects (instances of Project):
   this.data.forEach(function(project){
     if(project.active){
        // if it is active add it to our total
        total += project.getTotalCost();
     }
   });
   return total;
}

We can now execute this method from the command line:

Project.getCostOfActiveProjects();

Or once again, we can use JSON-RPC. Classes like Project are accessible by URI as instances of the Class metaclass:

POST /Class/Project

{
   "method":"getCostOfActiveProjects",
   "params":[],
   "id":"call2"
}

Throughout this post we have been creating our class definitions through the console by adding new properties. However, in real application development, you will probably want to define your classes using the class constructor in .js files in the WEB-INF/jslib directory. To define our class in .js file:

Class({
  id:"Project",
  "extends": Object,
  prototype:{
    addTask: function(task){
      ... all your prototype methods defined here
  },
  getCostOfActiveProjects: function(){
    ...
  }
});

Your .js file will be automatically loaded if it is in the WEB-INF/jslib directory, and automatically reloaded when it is changed.

Libraries?

With any platform, one should ask what libraries are available. Persevere is built on Rhino, which runs on the Java Virtual Machine (JVM) and provides access to the libraries for the JVM. These means that Persevere code has access to the plethora of Java libraries out there. With sockets, file IO, cryptography, and so on, virtually every possible library one could wish for is available on the Java platform.

Object Identity

Now suppose we want to add an existing task from another project to Project/1. That is we don’t want a copy of an existing task, but we want to maintain the identity of the task. If this task is modified (price is updated, or maybe it is marked as finished), we want this modification to be available in both referring Project instances (without making two modifications). At the console, in JavaScript, this is simple and naturally follows the semantics of JavaScript:

// get the second task from Project/2
var task = load("Project/2").tasks[1];
// add this task to project: 
load("Project/1").addTask(task);

In Persevere, even though objects are transparently persisted and restored, object identity is carefully preserved. Therefore if we referenced the same object from two different objects, we can still be assured of equality (this is assuming that the newly added task is the fourth task for Project/1):

load("Project/2").tasks[1] === load("Project/1").tasks[3] // -> true

Object identity is preserved for the parent object as well. Persevere guarantees that there is never more than one copy of any single persisted object (per object id) in memory at a time, helping Persevere be much more memory efficient):

load("Project/1") === load("Project/1") // -> true

JSON-RPC + JSON Referencing

The JSON-RPC examples for executing methods remotely haven’t included any parameters. Let’s take another look at the addTask to see remote execution with parameters. With JSON-RPC it is straightforward to pass primitives like strings, numbers, and booleans as parameters to a method, but the addTask takes an object. We can use a regular literal object in a JSON-RPC message to execute addTask for a particular Project instance:

POST /Project/1

{
   "method": "addTask",
   "params": [{"name":"execute a JSON-RPC request", "price": 12}]
   "id": "call3"

In the last section we saw how to preserve object identity in JavaScript, but is this possible remotely? Absolutely, thanks to JSON Referencing. To create the same effect as the example above, adding the second task from Project/2 to Project/1, but in JSON-RPC:

POST /Project/1

{
   "method": "addTask",
   "params": [{"$ref":"/Project/2.tasks[1]"}],
   "id": "call4"
}

This remote method call instructs Persevere to get the .tasks[1] from the Project/2 and adds it to the Project/1, maintaining object identity in the process.

Typing and runAt definitions for methods

A central part of Persevere’s classes is the ability to define property types using JSON Schema structure. Persevere also supports an extension of JSON Schema called JSON Schema Interfaces (JSI) for defining the method signatures for classes. With JSI we can define the required types for parameters and return values. This is valuable for providing integrity without actually having to code assertions, and for providing structured documentation of the interface of class. It also essentially adds typing to JavaScript with no new syntax. This topic will be explored in more depth in a later post, but for now, we will look at a simple example.

We can define an addTask method that requires the first parameter to be an object and should return a boolean (the implemented method on the prototype would need to be updated to return a boolean):

Project.methods = {
   addTask: {
      parameters: [{name:"task", type:"object"}],
      returns:{type:"boolean"}
   }
}

if we had an actual Task class, we could alternately define that the Task class is a required as the first parameter. We could also specify that this method should only be executed on the server with the runAt attribute (see next section for how to execute on the client):

Project.methods = {
   addTask: {
      parameters: [Task],
      returns:{type:"boolean"},
      runAt:"server"
   }
}

Projecting the object model into the client (with Dojo)

Since the Persevere object model is in JavaScript, it can naturally and coherently be delivered to the browser. With Dojo this is easy. We can simply use the PersevereStore, and the methods will automatically be available on the client. For more detailed information, read Using Dojo with Persevere. Here we will briefly look at how we can use the object model we just created in Dojo. We can load a set of stores corresponding to our Persevere tables/classes and then do a query to get some project objects:

dojo.require("dojox.data.PersevereStore");
var deferred = dojox.data.PersevereStore.getStores();
deferred.addCallback(function(stores){
	projectStore = stores.Project;
        projectStore.fetch({onComplete: function(results){
		allProjects = results;   
        }});
});

And now we can execute a Project method, just as we did on the server:

var firstProject = allProjects[0];
firstProject.getTotalCost(); // -> 32

Conclusion

Persevere provides a consistent and intuitive integration of JavaScript with object persistence and data storage. Persevere’s architecture is a perfect choice for client-centric web applications and “single page” applications that want the consistency of end-to-end JavaScript and JSON for a clean uniform object model in the server, across the wire, and in the client based on the properties pattern. In combination with Dojo or the Persevere JavaScript client library, data-based applications are developed with utmost efficiency.