Prudence
The Scalable REST/JVM
Web Development Platform

Creative Commons License

Handlers

Prudence supports a few kinds of "handlers," special purpose code for handling specified mechanisms.

Handlers vs. Resources

Creating a handler is similar to creating a resource, though handlers are even simpler.
All files under /handlers/ are assumed to be source code files. They are mapped simply to internal URIs, where a trailing slash is optional. Handlers do not have external URIs like resources. Though there are different kinds of handlers, they are all put in /handlers/, the difference being only in which entry points Prudence expects to find in them. It's up to you to use the correct kind of handler for the correct purpose.
The life cycle of handlers is similar to that of resources, except that no defrosting or preheating is supported.
Entry points work a bit differently:
  • Like in resources, the "conversation" service is provided as the first argument, but only a subset of the API is supported.
  • Some entry points support additional arguments.

Filters

Filters sit somewhere along the conversation route, and are able to affect the progress of the conversation. See the section on filtering in routing for a complete discussion.

Entry Points

Both entry points are optional, though it's quite useless to have a filter with neither.
handleBefore
This is called before the request is handled by the next handler along the route.
There is likely no response set at this point, so you only have access to request attributes.
Three literal return values are supported, as either a string or a number:
  • "continue" or 0: Continue to the next handler
  • "skip" or 1: Skip the next handler and continue to our handleAfter entry point
  • "stop" or 2: Stop our handling altogether (note that previous handlers along the route may still process the conversation before it returns to the client)
Otherwise, you may return an internal URI, which must begin with a "/". This causes the filter to capture to that URI (an internal redirect). This is a powerful feature allowing you to implement complex capturing via filters. For more information and an example, see dynamic capturing in the routing chapter.
handleAfter
This is called after the request is handled by the next handler along the route, unless "stop" was returned from our handleBefore.
At this point, a response has usually been set, unless we returned "skip" from in handleBefore or another filter down the route erased it. You may manipulate the response or even replace it entirely.
Note that handleAfter is called even if the next handler encounters an error. You can test for such an error with conversation.statusCode. Indeed, one use case for filters is to clean up and release hanging services after errors occur.
No return value is expected.

Cache Key Pattern Handlers

These handlers are used to set conversation.locals that are then used for filling in custom document.cacheKeyPattern variables. If, and only if, the variable is used in a cache key pattern, then the handler is called. It is expected that the handler would then fill the appropriate conversation.local with a value, which is then used to cast the pattern into a value.
Custom variables for sophisticated, powerful caching strategies. For example, by including a user identifier in cache key patterns, you can allow for caching pages or fragments per user.

Global Handlers

One way to use them is to set them globally, such they will be usable in cache key patterns of any document. To do so, in your application's "routing.*" use the cacheKeyPatternHandlers service, which is a map of variable names (without the curly brackets) to handler names. For example, let's have "{un}" and "{ug}" be handled by "/handlers/userinfo.js". Here the "routing.js":
cacheKeyPatternHandlers.put('un', 'userinfo/')
cacheKeyPatternHandlers.put('ug', 'userinfo/')

Local Handlers

You can also set cache key pattern handlers for specific documents, using document.cacheKeyPatternHandlers. Local settings will override global settings. For example, here's a scriptlet from a document in /web/dynamic/ or /web/fragments/:
document.cacheKeyPatternHandlers.put('un', 'userinfo/')
document.cacheKeyPatternHandlers.put('ug', 'userinfo/')
document.cacheKeyPattern = '{an}|{ptb}|{dn}|{un}' 

Entry Points

handleCacheKeyPattern
The second argument of this entry point, after the "conversation" service, is a list of the variable names that appear in the document.cacheKeyPattern, and which you have registered to handle by calling document.cacheKeyPatternHandlers. The variable names here are not enclosed in curly brackets.
A good technique is to iterate this list and fill in an appropriate conversation.local for each variable. He is a "/handlers/userinfo.js" to match the example above:
function handleCacheKeyPattern(conversation, variables) {
	var cookie = conversation.getCookie('session')
	var user = getUserFromCookie(user)
	for(var i in variables) {
		var variable = String(variables[i])
		switch(variable) {
			case 'un':
				conversation.locals.put('un', user.name)
				break
			case 'ug':
				conversation.locals.put('ug', user.group)
				break
		}
	}
}
​
function getUserFromCookie(cookie) {
	...
	return user
}
Note that no return value is expected.

Scriptlet Plugins

These powerful handlers let you create your own scriplet types for generating HTML, effectively extending Prudence's Scripturian-based templating engine.
Though you can always simply call a function from a regular scriptlet, scriplet plugins can be more succint, elegant and readable. In particular, you can use them to break limitations of your programming language, because they let parse content as you see fit. Thus, they are useful for embedding templating engines and expression languages.
You can install scriptlet plugins in your application's "routing.*" using the scriptletPlugins service, which is a map of plugin code to handler names.
In this example we'll have "[" and "]" be handled by "/handlers/paragraph-scriptlets.js". Here is the "routing.js":
scriptletPlugins.put('[[', '/paragraph-scriptlets/')
scriptletPlugins.put(']]', '/paragraph-scriptlets/')
Note that you can also install instances of ScriptletPlugin into scriptletPlugins directly. This is useful for installing plugins written in Java or supplied by a third party JVM library.

Entry Points

getScriptlet
In addition to the "conversation" service argument, this entry point receives two more arguments: code and content. The code is which scriptlet plugin code you need to interpret, and content is the content of the scriptlet. All you need to do is return the resulting scriptlet.
Here's our "/handlers/paragraph-scriptlets.js":
function handleGetScriptlet(conversation, code, content) {
	switch (String(code)) {
		case '[[':
			return 'print(\'<p style="' + content + '">\');'
​
		case ']]':
			return 'print(\'</p>\');'
	}
​
	return ""
}
The example is trivial: scriptlets can generate any code, not just "print" statements.
Here's an example of how these scriptlets would be used in generating HTML:
<html>
<body>
<%[[ color: red; %>
This is a red paragraph.
<%]]%>
</body>
</html>
Note that even though changes to your scriptlet plugin handler code take effect on the fly, the documents that use this handler are not regenerated on the fly when scriptlet handler code is updated. The reason is that the handler code is used only when compiling your document, not when its actually executed. If you want Prudence to regenerate your documents, either make a trivial change to them (for example, "touch" to update their date) or delete Prudence's /cache/ directory and restart your Prudence instance.