Hosted by Three Crickets

Prudence
Scalable REST Platform
For the JVM

Prudence logo: bullfinch in flight

Tutorial

The source code for the examples in the tutorial is available for download.

Up and Running

All you need to run Prudence is a JVM. Version 7 or above is recommended, though version 6 can also be supported. You do not need the "Enterprise Edition" nor even a "JDK": Prudence needs only the basic JVM runtime.
There are two ways to get Prudence: the easiest way is to download the distribution, which includes most everything and is ready to rumble. Let's do that now.
But Prudence is modular: later on, you may prefer to assemble your own distribution using the Sincerity packaging and bootstrapping tool.

The Command Line

Actually, the distribution you downloaded comes bundled with Sincerity, which we will use to start Prudence.
Before we do that, let's quickly familiarize ourselves with Sincerity. Open a command terminal and change to the Prudence directory. We'll call it our "container." If you're using a *nix operating system, you can run the command as "./sincerity". In Windows it's "sincerity.bat".
To see a list of all Sincerity commands:
sincerity help
For example, let's print out all installed modules:
sincerity dependencies
If your operating system supports GUIs, you can start Sincerity without any command to start its GUI (or use the "gui" Sincerity command):
sincerity
The GUI is useful for visualization, but generally the CLI is better for getting things done. We'll be using the CLI in this tutorial.

Ready… Set…

Go!
sincerity start prudence
The command will print out some version information, tell you which applications are installed, and then hold. You should now be able to point your browser to http://localhost:8080 in order to see the Prudence administration application. Or, just go right to http://localhost:8080/stickstick/ and http://localhost:8080/prudence-example/ to see the included examples. Stickstick is an example of an "AJAX" application, implementing sticky notes shared on the web and saved to a relational (SQL) database. It comes with the H2 database, so it does not require a separate database server. The Prudence Example is more general, walking you through Prudence's basic features in all supported programming languages.
While playing around with the examples, you may also want to look at the logs: they are in the container's "/logs/" directory. "web.log" is an NCSA-style log of all web requests. Logs prefixed with "prudence-" are created per application, and "common.log" is used for everything else. The logs will automatically roll, so don't worry about them overflowing your storage.
To quit Prudence, press CTRL+C.

Further Exploration

First Steps

You've played with the example applications, and now it's time to create your own. Let's make a basic CMS (Content Management System), a web site where users can change the content of pages and create new ones via the web!
Create a new application using the "prudence" Sincerity command:
sincerity prudence create cms
Restart Prudence, and you should be able to see your new application at http://localhost:8080/cms/. There's not much to see at this point.

Our Application's Subdirectory

Let's take a look at our application. Its files are all under "/component/applications/cms/".
At the root are three JavaScript files used to configure the application. Take a look at "routing.js" and "setting.js". The former defines your application's URI-space, while the latter controls its general behavior. Note that Prudence's (and Sincerity's) configuration philosophy is to use JavaScript wherever possible, which allows for dynamic, flexible configurations that adapt to the environment in which they are run.

Resources

The "/resources/" subdirectory is mapped directly to your URI-space, just like most standard web servers. For example, a file named "/mydir/myfile.html" will be mapped to http://localhost:8080/cms/mydir/myfile.html. Also like standard web servers, "index.html" will be mapped to the directory itself.
Actually, you'll notice that our root index is named "index.t.html". We call that "t" a pre-extension: here, the "t" stands for "template," and tells Prudence that this is a text file that may include one or more sections of code called "scriptlets," which are delimited by "<%" and "%>". This, again, may be familiar to you: it's similar to how PHP, JSP and ASP work.
Under "/resources/style/" you'll see "site.less". LESS is a powerful extended CSS language: Prudence will automatically recognize the ".less" extension and create the CSS for us.

Libraries

Our reusable code is all under the "/libraries/" subdirectory. Specifically, "/libraries/includes/" contains fragments that can be included into our template pages. You'll see that our "index.t.html" uses them. For example:
<%& '/header/' %>
…includes the file "/libraries/includes/header.html". Note that included files may have scriptlets in them (and can be cached independently).
All the above might seem quite familiar to you, and this is by intention. But the files under "/libraries/dispatched/" may seem strange: they are "low-level" direct implementations of encapsulated RESTful resources. Take a look at "example.js".
Moreover, these kinds of RESTful resources are not directly mapped to URIs like those in the "/resources/" subdirectory. Instead, they are "dispatched" using unique IDs. In Prudence, this paradigm is called "URI/resource separation," and it allows you full flexibility in structuring your code vs. structuring your URI-space.
We'll get into all that as we go along this tutorial.

Scriptlets

Let's look more closely at our "index.t.html" page. The scriptlets are executed, while what's outside the scriptlets is simply output to the HTTP response. You can freely mix regular output and scriptlets:
<% for (var x = 0; x < 10; x++) { %>
<p>These are 10 paragraphs.</p>
<% } %>
There are also a few shortcut scriptlets. For example, to output an expression:
<%= x*2 %>
The above is equivalent to:
<% print(x*2) %>
Also useful is the inclusion scriptlet, which we've already discussed:
<%& '/header/' %>
The above is equivalent to:
<% document.include('/header/'); %>
Behind the scenes, Prudence parses "index.t.html" and turns it into JavaScript source code. If you'd like to see the generated code, look at the Scripturian cache, specifically under the container's "/cache/scripturian/container/component/applications/cms/resources/" directory. These generated files are not actually used by Prudence, and only available for debugging purposes. Disable their creation by configuring "debug" mode to false.

APIs

That "document" namespace mentioned above is part of the rich, well-documented API provided by Prudence. Other useful namespaces are "conversation," for accessing the request/response, and "application" for shared application-wide services. The complete API reference is available online, though it can be daunting to start there. Instead, continue reading through this tutorial, where we'll explain APIs as we go along, and eventually hit the manual: especially important is the web data chapter, which goes over much of the interaction with clients.
And remember that you're running on the JVM: the entire JVM standard library is available for you, as well as any other JVM library you install in your container (using Sincerity or manually).

Hello, World

Let's create a new page for our CMS, under our application's "/resources/" subdirectory. We'll call it "page.t.html". We'll start simple:
<html>
<body>
	<p>Hello, world. This page will become editable very soon!</p>
</body>
</html>
Browse to http://localhost:8080/wiki/page/ to see it. Note that you do not need to restart Prudence if you're only adding or changing pages: Prudence will pick up these changes on-demand, on-the-fly, and make sure to compile and recompile as necessary.
You'll notice that even though the file has a ".t.html" extension, Prudence does not use the extension for the URL. This is because that extension is entirely an implementation concern on your end: there's no reason for the user to have to see it, nor to have it clutter the URL.
Also notice the trailing slash: it's "page/", not "page". Prudence enforces trailing slashes. This allows your relative URLs to be clear and unambiguous. For example, to insert an image in our page, we could do this:
<img src="../images/logo.png" />
Without a trailing slash, you would not need the "../", but with the trailing slash, it's necessary. If you don't pick one of the two options, handling relative URLs quickly becomes messy. So why have we decided to standardize on trailing slashes? Because it gives you more flexibility: for example, instead of saving our page "page.t.html", we could have saved it as "page/index.t.html", and the URL would be identical. As you will see, the trailing slash principle is used throughout Prudence to allow many such conveniences, for both external URLs and for internal library URIs. You'll see us constantly ending our URIs in slashes.

Further Exploration

Let's Make a CMS

It's time to flesh out out "page.t.html":
<html>
<body>
<%
document.require('/prudence/resources/')
​
if (conversation.request.method.name == 'POST') {
	var form = Prudence.Resources.getForm(conversation, {content: 'string'})
	var content = form.content
	application.globals.put('page', content)
}
else {
	var content = application.globals.get('page') || 'This page is empty.'
}
%>
<div style="border: 1px solid black; padding: 5px 5px 5px 5px">
	<%= content %>
</div>
<form method="post">
	<p>Edit this page:</p>
	<p><textarea name="content" cols="80" rows="20"><%= content %></textarea></p>
	<p><button type="submit">Submit</button></p>
</form>
</body>
</html>
Not the most exciting CMS, but it works for editing a single page. Let's break it down:

Infinite Editable Pages

The above is just a single page: let's multiply it by infinity.
To do this, we'll configure our URI-space by editing the application's routing.js file. To the "app.routes" dict, let's add the following:
app.routes = {
	...
	'/page/*': '/page/'
}
What this does is "capture" all incoming URLs that begin with "/page/" to exactly our "/page/". The "*" at the end of a URI template signifies a wildcard: anything may follow (except nothing). Restart Prudence for our routing.js changes to take effect.
You'll see that now, indeed, any URL beginning with "/page/" will take us to our single, lonely CMS page. For example, this: http://localhost:8080/cms/page/test/. If you edit one page, the content of all pages would change.
Let's edit our template in "page.t.html" to make it differ per incoming URL:
<%
document.require('/prudence/resources/')
​
var id = 'page.' + conversation.wildcard
​
if (conversation.request.method.name == 'POST') {
	var form = Prudence.Resources.getForm(conversation, {content: 'string'})
	var content = form.content
	application.globals.put(id, content)
}
else {
	var content = application.globals.get(id) || 'This page is empty.'
}
%>
And… that's it. Now any URL beginning with "/page/" becomes an editable CMS page. If that page does not exist it, it will be created. Note that we're using "page." as a prefix for our application globals in order to make sure they don't overlap with globals used for other purposes.
The only real "trick" here is the conversation.wildcard API, which gives us the captured wildcard we configured in routing.js.

Further Exploration

A Persistent CMS

So far in this tutorial, we've been storing the CMS data in RAM. To store the data on disk, we have a vast array of options: you can use any JVM database driver directly from Prudence.
For this tutorial, we'll be using MongoDB: a document-oriented database with many useful features, such as atomic operations, aggregation and map/reduce, and support for flexible horizontal scalability configurations. It uses JavaScript internally, and thus is a natural fit for web programming in Prudence: you can use JavaScript on the server, JavaScript on the client, and JavaScript in the database. This combo is so popular that we've gone so far as to add especially nice integration with Sincerity/Prudence.
You'll need to install a MongoDB server yourself for this section of the tutorial. If you can't do that right now, don't worry: the rest of the tutorial will work just fine using the in-memory storage we've been using so far, and you can just read through this part.
Let's install the MongoDB driver into our container using a Sincerity command:
sincerity add mongodb.javascript : install
We can configure the driver by editing our application's settings.js, and adding a section similar to this:
app.globals = {
	mongoDb: {
		defaultUris: 'localhost:27017',
		defaultDb: 'cms'
	}
}
You can provide full MongoDB connection string URIs, either a single one or an array if you are connecting to a replicaset. The driver will automatically create a thread-safe connection pool upon first use using these settings.
Our new MongoDB-enabled scriptlet:
<%
document.require(
	'/prudence/resources/',
	'/mongo-db/')
​
var id = 'page.' + conversation.wildcard
​
var collection = new MongoDB.Collection('pages')
​
if (conversation.request.method.name == 'POST') {
	var form = Prudence.Resources.getForm(conversation, {content: 'string'})
	var content = form.content
	collection.upsert({_id: id}, {$set: {content: content}})
}
else {
	var doc = collection.findOne({_id: id})
	var content = doc ? doc.content : 'This page is empty.'
}
%>
Restart Prudence, and check out your new persistent CMS.
Our usage of the MongoDB API is quite trivial here, but in case you're new to it, read up about the $set operation: it guarantees an atomic update of particular fields in our document.
For the rest of this tutorial, we'll continue to use in-memory storage in our examples, but feel free to adapt them to use MongoDB if you already have it set up!

Further Exploration

A Scalable CMS

Caching

As long as we're storing our CMS content in MongoDB or in memory, page rendering will be very fast and light. However, if we were to do more heavy lifting per page—for example, multiple database lookups—then performance would drop accordingly. By smartly caching our pages we can ensure that pages would be rendered only when necessary, allowing for the best possible performance.
Actually, talking about performance is a shorthand for the real issue: even pulling data from a database is usually very fast. The real issue is scalability: increased load on the database per user request can limit your ability to support many users. Obviously, it's most efficient to do work only when you need it: and that simple principle is the most important tool you have for increasing scalability.
Prudence does caching very well. So, though it might be considered an "advanced" topic, it's a crucial one, and worth going over in this tutorial. It's also easy:
<%
document.require('/prudence/resources/')
​
var id = 'page.' + conversation.wildcard
caching.duration = 60000
caching.tags.add(id)
caching.onlyGet = true
​
if (conversation.request.method.name == 'POST') {
	var form = Prudence.Resources.getForm(conversation, {content: 'string'})
	var content = form.content
	application.globals.put(id, content)
	document.cache.invalidate(id)
}
else {
	var content = application.globals.get(id) || 'This page is empty.'
}
%>
What we've done:
It's easy to see the caching in action via your browser's developer tools. In Firefox, turn on the network monitor by pressing CTRL+SHIFT+Q. In Chromium/Chrome, use CTRL+SHIFT+I and select the "network" tab.
Refresh the page and you will see the "GET" request you just sent. Click on it to see its details, and you will see the response headers returned from the server. The "X-Cache" family of headers will tell you the status of the server-side cache, such as whether it's a "hit" or "miss." You can disable these debug headers by configuring "debug" mode to false.
However, Prudence also utilizes the client-side cache: if you continue refreshing the page from the same browser within the 60-second cache window, the web browser's conditional HTTP requests will return a 304 "not modified" status, which tells the browser that there's no new information on the server, and thus it will efficiently avoid downloading the complete response, using its locally cached value instead.
You've just vastly improved the scalability of your CMS, allowed for a faster user experience, all without ever compromising on the freshness of user data. Prudence caching is pure win.
Note that editing your "page.t.html" file will automatically invalidate the cache, allowing you to instantly see your changes. Actually, this feature extends to the use of includes: if you edit any file, it will also invalidate all files that include it.

Compression

Prudence will automatically compress responses (using gzip or DEFLATE) if their size in bytes exceeds a certain threshold. Our CMS pages were likely too small, which is why you likely haven't seen compression. You'll usually want that threshold: there are little or no bandwidth savings to be gained by compressing tiny responses, so the extra CPU load required for compression will be wasted. For now, let's lower the threshold, just for demonstration purposes.
Open the application's setting.js and edit the app.settings.compression setting:
app.settings = {
	...
	compression: {
		sizeThreshold: 0,
		exclude: []
	}
}
Restart Prudence for the setting to take effect. With a threshold of zero bytes, all responses should now be compressed—assuming the client accepts compression, as indeed web browsers do.
Actually, Prudence integrates compression with caching: both compressed and uncompressed versions of the response are cached, so that compression will not have to be redone. This can result in significant savings for CPU usage in high loads. (And if a client needs a different kind of compression than the one that was stored in the cache—say, gzip was stored, but the client needs DEFLATE—then Prudence will use the uncompressed cache entry, compress it, and then cache. Smart!)
Let's see it in action. Using the web browser's network monitor, you'll notice that the response headers now include "Content-Encoding", which means that our responses are compressed. gzip or DEFLATE will be chosen according to the web browser's preferences. Other than that (and the different response byte size) it should look identical.
To test using cURL, we'll have to do two things: explicitly tell the server that we support compression by sending the "Accept-Encoding" header, and also be ready to uncompress the result in order to display it, using cURL's "compressed" flag:
curl --verbose --header 'Accept-Encoding: gzip' --compressed http://localhost:8080/cms/api/test/

Further Exploration

A CMS API

Our CMS is currently limited to web pages, but we can make it much better by opening it up as a RESTful web API. This would allow all kinds of clients to access our CMS, such as dedicated mobile apps, desktop applications, and "rich" web applications (using "AJAX"). It would also allow 3rd-party web sites and services to use our CMS as a service, rather than as an application (in business buzzword land this is called "SaaS").

Hello, API World

As we've seen, templates are Prudence's way of making scalable web pages. The other side of Prudence is manual resources.
We'll want our API in the "/api/" URL, so let's create a "api.m.js" file:
function handleInit(conversation) {
	conversation.addMediaTypeByName('application/json')
}
​
function handleGet(conversation) {
	return '{"message": "Hello, API world."}'
}
Remember those pre-extensions? "m" stands for "manual." The ".js" extension means that our resource is implemented in JavaScript, though we can end it in ".py", ".rb", etc., if we want to use other supported programming languages.
You could test the resource in a web browser by browsing to http://localhost:8080/wiki/api/, however that's not the best way. Also, the web browser only knows how to do "GET" by default. So instead, we recommend testing APIs using cURL.
Let's test our API using this cURL command line:
curl http://localhost:8080/cms/api/
Hi!

Fleshed Out

We can now fully flesh out our API. It will look quite similar to the template we wrote above:
document.require(
	'/prudence/resources/',
	'/sincerity/json/')
​
function handleInit(conversation) {
	conversation.addMediaTypeByName('application/json')
}
​
function handleGet(conversation) {
	var id = 'page.' + conversation.wildcard
	var content = application.globals.get(id) || 'This page is empty.'
	return Sincerity.JSON.to({content: content})
}
​
function handlePut(conversation) {
	var id = 'page.' + conversation.wildcard
	var payload = Prudence.Resources.getEntity(conversation, 'json')
	application.globals.put(id, payload.content)
	document.cache.invalidate(id)
	return Sincerity.JSON.to({content: payload.content})
}
​
function handleDelete(conversation) {
	var id = 'page.' + conversation.wildcard
	application.globals.remove(id)
	document.cache.invalidate(id)
	return null
}
What we did:
We'll also need to change our routing.js to capture the wildcard, again similar to what we did with the template page:
app.routes = {
	...
	'/page/*': '/page/',
	'/api/*': '/api/'
}
Restart Prudence for the routing.js change to take effect, and then test it again:
curl http://localhost:8080/cms/api/test/
Now, let's test an HTTP "PUT" with new page content:
curl --request PUT --data '{"content": "New page content!"}' http://localhost:8080/cms/api/test/
And we can also "DELETE":
curl --request DELETE http://localhost:8080/cms/api/test/
curl http://localhost:8080/cms/api/test/
While testing with cURL, you can simultaneously test the CMS pages using the browser, as we did earlier. Both the API and the pages see the exact same data, as it's stored in the application.globals.

Cached

Let's add support for caching our API results. Again, it's very similar to what we did with our template, except that caching should be set up in handleInit:
function handleInit(conversation) {
	conversation.addMediaTypeByName('application/json')
	var id = 'page.' + conversation.wildcard
	caching.duration = 60000
	caching.tags.add(id)
}
Note that the cache tag is exactly the same as we're using for template pages: this guarantees that the template pages will be updated even though the CMS is changed from here, and vice versa, even though what is being cached is quite different. Because the cache key depends on the URI, and the URIs for the pages and APIs are different, there will be no conflict.
To see the caching headers when using cURL, add the "verbose" flag:
curl --verbose http://localhost:8080/cms/api/test/
curl --verbose --request PUT --data '{"content": "New page content!"}' http://localhost:8080/cms/api/test/

Further Exploration

Finishing Touches

Here, we'll beef up our CMS a bit, and along the way introduce you to a few more Prudence features.

CSS with LESS

Prudence comes with LESS built in: it's an extended CSS language. LESS greatly increases the power of CSS by allowing for code re-usability, variables and expressions, as well as nesting CSS.
Let's "less up" our CMS pages! First, let's include the CSS in our "page.t.html" by adding an HTML "head" tag:
<html>
<head>
	<link rel="stylesheet" type="text/css" href="<%.%>/style/site.css" />
</head>
You'll notice our use of "<%.%>": this is a shortcut to printing out the conversation.base API, which returns a relative URI path from our currently requested URI to the base URI of the application. For example, if our URI is "http://localhost:8080/cms/page/one/two/three/" then conversation.base would be "../../../..". This frees us from having to hardcode complete URI paths, and guarantees portability even if we move the application to a different URI, attach it to multiple virtual hosts, or run it behind a reverse-proxy.
Now, let's edit the "/resources/style/site.less" file, which was created for us from the application template. Note that there's also a ".css" file in that directory: Prudence will update it for us when we edit the ".less" file. How about this:
@sans-serif: Lucida Sans Unicode, Lucida Grande, Verdana, Arial, sans-serif;
@color1: #EEEEEE;
@color2: #003300;
​
body {
	font-family: @sans-serif;
	font-size: 12px;
	background-color: @color1;
	color: @color2;
	textarea {
		background-color: @color2;
		color: @color1;
	}
}
As a final treat, Prudence can also minify the CSS file for us, in order to save some bandwidth or obfuscate it (Prudence will also compress it for us using gzip or DEFLATE, as was mentioned above). To turn on minification, simply change the resource URI to include ".min":
<head>
	<link rel="stylesheet" type="text/css" href="<%.%>/style/site.min.css" />
</head>

Markdown

Another stylistic flourish would be to use an HTML markup language instead of raw HTML. It's useful for this tutorial, because it will show us how easy it is to use JVM libraries from within Prudence.
First let's install our library using Sincerity. We'll use Pegdown, a JVM implementation of Markdown:
sincerity add org.pegdown pegdown : install
Now let's edit our "page.t.html" to render using the Pegdown API:
<div style="border: 1px solid black; padding: 5px 5px 5px 5px"> 
	<%= new org.pegdown.PegDownProcessor().markdownToHtml(content) %>
</div>
After restarting Prudence we would be able to use Markdown in our CMS. Try to edit a CMS page using this content:
This is a title
===============
​
And a subtitle
--------------
​
* And these
* Are bullet points
* In a list

Internal API

You may have noticed that there's some duplication in our code: both the CMS pages and the CMS API use application.globals to store the content. In this case it's trivial code, but if we moved to database storage, for example, the code would surely grow and be worth encapsulating as an API. One way we could do this is create a reusable library, for example "/libraries/data.js", which we could use both in the CMS pages and the CMS API using "document.require".
But an interesting shortcut is suggested in the fact that we already have an encapsulated API: our CMS web API. We could simply use that directly. The apparent disadvantage is that it is a web API, and we would have to go through HTTP to call it. However, in Prudence you can call any resource internally, bypassing HTTP entirely, making such requests as fast as any function call.
Let's modify our "page.t.html" for this:
<%
document.require('/prudence/resources/')
​
var id = 'page.' + conversation.wildcard
caching.duration = 60000
caching.tags.add(id)
caching.onlyGet = true
​
if (conversation.request.method.name == 'POST') {
	var form = Prudence.Resources.getForm(conversation, {content: 'string'})
	var content = form.content
	Prudence.Resources.request({
		uri: '/api/' + conversation.wildcard,
		method: 'put',
		payload: {type: 'json', value: {content: content}}
	})
}
else {
	var data = Prudence.Resources.request({
		uri: '/api/' + conversation.wildcard,
		mediaType: 'application/json'
	})
	var content = data ? data.content : 'This page is empty.'
}
%>
We've used the Prudence.Resources.request API to make the request: it will automatically make an internal request if the URI begins with "/" (rather than a protocol, such as "http:").
There's actually yet another optimization we can make: we are currently bypassing HTTP, but it's also possible to bypass JSON serialization. That optimization is more advanced, and is fully explained elsewhere.

Further Exploration

Not Only JavaScript

The application skeleton uses JavaScript (server-side JavaScript; it has nothing to do with the code running inside web browsers), but Prudence also supports Python, Ruby, PHP, Lua, Groovy and Clojure.

PyCMS

For this tutorial, we'll show you how to implement our entire CMS in Python.
sincerity add python : install
We'll also need a Python JSON library, because our Python engine, Jython, doesn't come with one. simplejson works well with Jython, so let's install it:
sincerity easy_install simplejson
Now let's rewrite our "page.t.html" using Python instead of JavaScript for our scriptlets:
<%py
from org.pegdown import PegDownProcessor
​
id = 'page.' + conversation.wildcard
caching.duration = 60000
caching.tags.add(id)
caching.onlyGet = True
​
if conversation.request.method.name == 'POST':
    content = conversation.form['content']
    application.globals[id] = content
    document.cache.invalidate(id)
else:
    content = application.globals[id] or 'This page is empty.'
%>
<div style="border: 1px solid black; padding: 5px 5px 5px 5px"> 
	<%= PegDownProcessor().markdownToHtml(content) %>
</div>
Note the "<%py" delimiter, telling us that from now on scriptlets will be in Python. We can switch back to JavaScript using "<%js", or indeed to any other supported language. You can also change the default from JavaScript to Python if you prefer.
For our "api.m.js" file, we will have to rename it to "api.m.py". Here's the code in Python:
import simplejson
​
def handle_init(conversation):
    conversation.addMediaTypeByName('application/json')
    id = 'page.' + conversation.wildcard
    caching.duration = 60000
    caching.tags.add(id) 
​
def handle_get(conversation):
    id = 'page.' + conversation.wildcard
    content = application.globals[id] or 'This page is empty.'
    return simplejson.dumps({'content': content})
​
def handle_put(conversation):
    id = 'page.' + conversation.wildcard
    payload = simplejson.loads(conversation.entity.text)
    application.globals[id] = payload['content']
    document.cache.invalidate(id)
    return simplejson.dumps({'content': payload['content']})
​
def handle_delete(conversation):
    id = 'page.' + conversation.wildcard
    del application.globals[id]
    document.cache.invalidate(id)
    return None
Some things to note:

CljCMS?

Well, to keep this tutorial short, we won't go on rewriting our CMS in every supported programming language… but here's some proof that they do work.
Let's install Clojure:
sincerity add clojure : install
Now let's create a Clojure manual resource, at "/resources/hi.m.clj":
(defn handle-init [conversation]
  (.. conversation (addMediaTypeByName "text/plain")))
​
(defn handle-get [conversation]
  "Hello from Lisp!")
Browse to http://localhost:8080/wiki/hi/ to see it.

Further Exploration

What's Next?

We purposely didn't cover every single Prudence feature in this tutorial: just enough to get you started. Continue reading through the Basic Manual as necessary, refer to the online API documentation, and take a peek at the Advanced Manual to see just how far you can go.
Here are some highlights of topics we haven't covered:

The Prudence Manual is provided for you under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The complete manual is available for download as a PDF.

Download manual as PDF Creative Commons License