Getting Started with Persevere Using Dojo

By on July 23, 2008 6:48 am

This post is written for Persevere 1.0 and Dojo 1.3. If you want to get started with Persevere 2.0 (recommended), take a look at this post. Dojo 1.6 and newer has a new data API as well.

The Persevere server is an open source JSON application and storage server. Persevere pairs well with Dojo; the Dojo Data paradigm has a strong correlation to Persevere’s data storage structure. Virtually everything you can create or action you can perform with Dojo Data can be persisted in Persevere’s data storage including subobjects, arrays, circular references, and functions. Combining Persevere with Dojo allows you to rapidly develop simple database applications with create, read, update, and delete (CRUD) capabilities with minimal effort.

Starting Persevere

To get Persevere running, first download the latest version of Persevere. Next, unzip the Persevere zip file into the directory you want to use for Persevere. Finally, go to the directory where you unzipped Persevere and run:

java -jar start.jar

Persevere should start up as long as Java is installed on your system. You can now access Persevere in your web browser by visiting http://localhost:8080/.

Creating a Persevere Class

The basic container for storage in Persevere is a class; analogous to a table in relational databases. A Persevere class holds object instances, and class style structures can be defined for the object instances including methods and type definitions for the object properties. To create a Persevere class, first open the object browser at http://localhost:8080/browser.html?id=root. The object browser allows you to browse and navigate the objects in Persevere. To create a class, click the button (make sure no object is selected). A dialog will ask you for the name of your class and what class to extend. We will call the class “Product” and we will accept the default base class of “Object”. You now have data storage structure which can hold Product object instances.

Connecting with Dojo

Persevere is compliant with the HTTP/REST protocol, so the JsonRestStore is very effective with Persevere. However, dojox.data.PersevereStore is an extension of JsonRestStore specifically designed to simplify connecting to Persevere and take advantage of extra features of Persevere. This architecture is designed for a data store instance per server table. With the PersevereStore we can easily get a set of data stores corresponding to the server tables/classes:

dojo.require("dojox.data.PersevereStore");
var deferred = dojox.data.PersevereStore.getStores();

This deferred object will provide an object map of the stores when finished. We can get the data store for our Product table:

deferred.addCallback(function(stores){
	productStore = stores.Product;
});

The data store can be used with various widgets that support data stores. For example to use this table with the Dojo Grid:

grid.setStore(productStore)

or you can set the productStore as the store in the constructor. For example:

grid = new dojox.grid.Grid({
	store: productStore,
	query: "",
}, dojo.byId("gridId"));

With the ability to define any type of object structure, a Persevere data store can be used with virtually any widget. With the referencing capabilities it can be used hierarchically, so it is also well suited for use with the Tree widget.

Create, Read, Update, and Delete

Via Dojo, interacting with Persevere can be done directly with the Dojo Data API. To create our first object instance in the Product table:

paintballGun = productStore.newItem({name:"Paintball Gun",price:129.99});
productStore.save();

We can easily read properties using directy property access or the Dojo Data API:

paintballGun.price -> 129.99
productStore.getValue(paintballGun,"price") -> 129.99

We can update properties:

productStore.setValue(paintballGun,"price",119.99);
productStore.save();

Note that we do not need to define any type of schema, columns, or structure for a table ahead of time, we can simply dynamically create properties on objects that we create.

And delete the objects as well:

productStore.deleteItem(paintballGun);

One of the powerful aspects of Persevere is that you can dynamically persist a large variety of data structures. For example, we could save a sub object with manufacturer information:

productStore.setValue(paintballGun,"manufacturer",{
	name:"HotShots",
	rating:4,
	started: new Date(Date.parse("Jul 09 2002")),
	topProduct:paintballGun
});

In this example, we are saving an object as a value, and even including Dates, and a circular reference (back to the product object). Virtually anything you can create with JavaScript can be persisted to Persevere.

Querying

An essential aspect of database interaction is querying. Here we can use the standard query-object convention used by Dojo Data to search for objects. A query object is an object with name-values corresponding to the search filters to apply (and wildcards are supported). For example to find all the objects with a name that starts with “Paintball”:

productStore.fetch({
	query: {name: "Paintball*"},
	onComplete: function(results){
		results[0] -> paintballGun
	}
});

We can also utilize sorting and paging as well. To sort by price (lowest to highest) and return items 20-29:

productStore.fetch({
	query: {name: "*"},
	sort: [{attribute:"price",descending:false}],
	start: 20,
	count: 10,
	onComplete: function(results){
		...
	}
});

Persevere also supports JSONPath/JSONQuery queries, which have a much greater level of expressibility and can be used for more sophisticated queries. For example to search for all the items with a price less than $100 or a rating greater than 3:

productStore.fetch({
	query: "[?price<100 | manufacturer.rating > 3]",
	onComplete: function(results){
		...
	}
});

Live Data with Comet

Dojo and Persevere support HTTP Channels which allows for addition of live data updates through Comet notifications. Adding Comet capability is simple, just add the HttpChannels module after the PersevereStore has been loaded:

dojo.require("dojox.data.PersevereStore");
dojo.require("dojox.cometd.HttpChannels");

This is all that is necessary. Dojo will automatically subscribe to all data that is accessed, notifications will be delivered to the client, and the notifications will result in cached data updates and Dojo Data API notifications (which will update the user interface on notification aware widgets like the Grid). No additional coding is necessary to utilize live data updates.

Setting up Security

Out of the box, security is disabled in Persevere to make it easier to begin development. However, security is a key feature of Persevere, allowing Persevere to be safely used directly on the web, accessible from your JavaScript environment. Prior to deployment you should of course enable security, and it is also enabled once the first user account is created. To create a user account, go to the object browser (http://localhost:8080/browser.html?id=root), click on the sign in button , and choose to create a new user. This first user is given the administrative privileges for the system. All data is accessible to this user, but is read only by default for all other users and public access. Additional users may be created by the same process (or programmatically), but subsequent users must be granted access to data.

To enable higher level access for the public or other created users, you can select an object or table and click on the grant access button in the object browser. You can then enter “public” for public access, or a user name to enable write access to the data.

Conclusion

You can see these simple data interaction techniques in sample code in the customers example included in the Persevere download (which can be viewed locally at http://localhost:8080/examples/customer.html) There are numerous other important capabilities of Persevere including server side JavaScript execution through JSON-RPC, schema definition, prototype objects, cross-site referencing, accessing existing SQL databases, and more that are described in the Persevere documentation and we will explore in future tutorials. You should now have enough information to get a quick start to building database applications with Persevere using the oft-used create, read, update, delete, and querying operations, and easily plugging into Dojo widgets, with support for live data updates. You can start building applications almost instantly; no need to create schemas or table definitions ahead of time, simply start creating and building dynamic persistent objects in your data stores, and connect them to widgets for display. You can build your entire application, client and server, in JavaScript.

Comments

  • Pingback: Comet Daily » Blog Archive » Persevere Tutorial()

  • Michael Ticknor

    Great article. Is there any performance/load/stress test data showing how the Persevere server handles large data sets / large data objects? Also can Persevere servers be clustered? Thank you!

  • Persevere servers can not be clustered at this time, although it is certainly on the roadmap, and the Persevere’s RESTful architecture is very well suited for efficient clustering. I have run some performance tests and posted the results at: http://groups.google.com/group/persevere-framework/browse_thread/thread/201103d9938c7a2f/ce8a1309be0b450c. There are number of big improvements for performance that I planning on implementing, but functionality and stability has been the primary focus for the initial release.

  • Pingback: SitePen Blog » Using the Persistent Object Model in Persevere()

  • Andrew Senior

    Won’t start on RedHat 5.3
    2009-02-12 15:28:14.554::WARN: EXCEPTION
    java.lang.ClassCastException: gnu.java.nio.ServerSocketChannelImpl cannot be cast to java.nio.channels.SocketChannel
    Starts on M$ XP but get “Could not load dojox data StoreExplorer
    ‘../dojox/data/StoreExplorer.js’ (which is there) when clicking the explorer button

  • @Andrew: The GCJ JVM on RedHat is really an incomplete implementation. You need a real JVM, install Sun’s on your RedHat box.
    As far as the StoreExplorer error, which version of Persevere are you using (beta or nightly)? Are you getting a 404 for the StoreExplorer.js? Out of the box, I can’t reproduce this error on XP, so if you have any info on your config, and what the URL is of the StoreExplorer.js that is failing, that would help.

  • Nancy

    Hi,

    I was curious to know how does the persevere back-end contact the data store in case of a data modification. I read a little about the Dojo Data API notifications, but wasn’t clear on how persevere does it. Learning about rest channels I get that it sends the http response back on /channels but how is the UI updated?

  • @Nancy: The RestChannels modules maintains a comet connection with /channels. Persevere sends data notifications out through the channels comet connection. The Dojo RestChannels module receives these data notifications and delivers them through dojox.data.restListener, which then publishes the dojo data notification events on the data store. The UI components (like the grid) listen (by using dojo.connect) for data store notification events and updates its view when they are received.

  • Nancy

    Hi Kris,

    This question is more for the RestListener. How does the rest listener connect to the store and how does the store update itself. Does the Rest message expected by the Rest Listner follow some format? I couldn’t find much information about rest listener on dojo. Hence would appreciate if you could point me to some sample where I can clearly understand the flow. I am trying to implement the part where the server notifications (Bayeux publishes) can update the UI.

  • @Nancy: The index that used by the JsonRestStore and it’s derivatives is updated when you run dojox.json.ref.fromJson (or resolveJson), which should be run on the JSON that is received from the server. The format of update messages are described here:
    http://cometdaily.com/2008/11/12/using-rest-channels-in-dojo/
    And then dojox.data.restListener connects to the store by looking at the source property and matching that to the proper store, and then directly calls the appropriate event on the store.

  • Tony

    Hi
    I’m just getting started with Dojo and Persevere, and I’ve run into a problem. I have a class, let’s call it MyClass. I’ve created a PersevereStore to access it, like so:
    dataStore = new dojox.data.PersevereStore({target:”/MyClass/”});
    This mostly works as I expect. I can successfully hook it up to a grid, etc.
    However, I am having trouble with queries. If I want to retrieve just the first instance of MyClass, I should be able to get it via the full path “/MyClass/[0]”. If I enter that into my browser’s address bar I get just what I expect; the first instance of the object. But if I try to query via the data store, I get nothing. For example, on a grid control:

    If I watch what is happening in Firebug I can see the query being made, and it appears to be correct, i.e.:
    GET http://localhost:8080/MyClass/%5B0%5D
    yet it returns nothing.
    Similar problems when I use a query with fetch(). Is this a bug, or am I doing something wrong?

    I am using Dojo 1.3.2 and Persevere 1.0rc2

  • Tony

    Sorry, the example in my previous post got eaten, so I’ll restate it. On a dojox.grid.DataGrid control connected to my data store, I had set the following attribute:
    query=”‘[0]'”

  • @Nancy: the restListener delegates by looking at the object id/url of the changed objects. Each JsonRestStore that is created has a different path for its objects (/SomePath/{id}), and the object ids are matched to the store target paths to determine which store should handle receive the event.

    @Tony: A query like /MyClass/[0] will return a single object, but a query expects an array of objects (which can be an array with a single object). To do this, you use this query /MyClass/[0:1], or even easier you can use the start and count parameters:
    query({start:0, count:1, query:”/MyClass/”, …});

  • Pingback: SitePen()

  • Christian

    I’ve been trying to display a folder-hierarchy using dijit.Tree / JsonRestStore / Persevere with an SQL datasource.

    The problem is that the Tree treats the array of childFolders as a single node “undefined”

    My Persevere config looks like this:
    {“id”:”Surveys.json”,
    “sources”:[
    {
    “name”:”folders”,
    sourceClass:”org.persvr.datasource.DatabaseTableDataSource”,
    driver:”com.mysql.jdbc.Driver”,
    connection:”jdbc:mysql://localhost/surv?user=XXX&password=XXX”,
    table:”folders”,
    camelCaseColumnNames: false,
    idColumn:”id”,
    dataColumns:[
    “name”,
    {
    foreignSource: “folders”,
    databaseColumn: “parent_id”,
    relationshipType: “many-to-one”,
    objectColumn: “parent”
    },
    {
    foreignSource: “folders”,
    foreignColumn: “parent_id”,
    foreignObjectColumn: “parent”,
    relationshipType: “one-to-many”,
    objectColumn: “childrenFolders”,
    },
    ],
    “schema”:{
    }
    }
    ]
    }

    As you probably can tell, the SQL table ‘folder’ has ‘id’, ‘name’ attributes and a ‘parent_id’ attribute (referencing another row).

  • @Christian: It is hard to tell from this example, can you include the JSON response that Persevere is sending to the client?

  • Christian

    Thanks for your time Kris,

    I think I may have a clue as to what’s going on.
    From what i can tell, TreeStoreModel assumes that store.loadItem “updates” the store, so that future calls to “store.getValue” returns the value immediately.
    For me, this doesn’t seem to be the case.

    Adding some logging to TreeStoreModel.getChildren():

    var onItem = function onItem(item){
    if(–_waitCount == 0){
    // all nodes have been loaded, send them to the tree
    onComplete(childItems);
    console.log(“A”, item);
    console.log(“B”, childItems);
    }
    }
    dojo.forEach(childItems, function(item){
    if(!store.isItemLoaded(item)){
    store.loadItem({
    item: item,
    onItem: onItem,
    onError: onError
    });
    }
    });

    Gives the following output:

    “A” [ Object $ref=”1.childrenFolders” __parent=Object __id=”/data/folders/1.childrenFolders” _loadObject=function(), Object $ref=”1.childrenFiles” __parent=Object __id=”/data/folders/1.childrenFiles” _loadObject=function() ]

    “B” [ Object id=”2″ name=”Arkiverade” __parent=[2] __id=”/data/folders/2″ …, Object id=”3″ name=”Kund AB” __parent=[2] __id=”/data/folders/3″ … ]

    “childItems” still holds the same (unresolved) references…

    Is it a problem that I am mixing items from two different stores in the Tree? (files and folders)