Prudence
The Scalable REST/JVM
Web Development Platform

Creative Commons License

API

Prudence exposes its API as a set of services to your source code. These services are available in scriptlets in /web/dynamic/, source code in /resources/, /handlers/, /tasks/, and in your configuration scripts.
Some notes for API use in different Prudence flavors:
Python
If you're using the Jepp engine, rather than the default Jython engine, you will need to use get- and set- notation to access attributes. For example, use application.getArguments() to access application.arguments in Jepp.
Ruby
Our Ruby engine, JRuby, conveniently lets you use the Ruby naming style for API calls. For example, you can use $application.get_global instead of $application.getGlobal.
JavaScript
Our JavaScript engine, Rhino, does not provide dictionary access to maps, so you must use get- and put- notation to access map attributes. For example, use application.globals.get('myapp.data.name') rather than application.globals['myapp.data.name'].
Clojure
You will need to use get- and set- notation to access attributes. For example, use (.getArguments application) to access application.arguments. You can use Clojure's bean form, for example (bean application), to create a read-only representation of Prudence services.

application

The same "application" service is shared between all code in a single application. Note that there is always a single application instance per application per component, even if the application is attached to several virtual hosts and servers.
The "application" service is a good place to store shared state for the application.
Though the "application" service of the configuration scripts is different from that of your Prudence application, you can access the Prudence "application" service in that application's "default.*" script as "applicationService". For example, you can access the application logger as "applicationService.logger".
application.globals, application.getGlobal
Application globals are general purpose attributes accessible by any code in the application.
Names can be any string, but the convention is to use "." paths to allow for unique "namespaces" that would not overlap with future extensions, expansions or third-party libraries. For example, use "myapp.data.sourceName" rather than "dataSourceName" to avoid conflict.
Though application.globals is thread safe, it's important to understand how to use it properly. Make sure you read the section on concurrency in "sharing state".
application.sharedGlobals, application.getSharedGlobal
These are similar to application.globals, but are in fact shared by all Prudence applications running in the instance.
Note that some secure Prudence deployments may disable sharing between applications, in which case there will be no shared globals. It is thus best to test that they exist before using them:
var dbConnection = application.globals.get('db.connection')
if((dbConnection === null) && (application.sharedGlobals !== null)) {
	// Was not in application globals, so try shared globals
	dbConnection = application.sharedGlobals.get('db.connection')
}
application.distributedGlobals, application.getDistributedGlobal
These are similar to application.globals, but are in fact shared by all members of the Prudence cluster to which we belong. Note that values stored here must be serializable. Depending on your object implementation, this may mean having to manually serialize/deserialize the value into a string or another serializable format.
This is simply a convenience API to access the Hazelcast map named "com.threecrickets.prudence.distributedGlobals". See application.distributedTask for another cluster-specific API.
application.arguments
Available only in configuration scripts. This is a list of command-line arguments provided to the Prudence instance script.
application.application
This is a reference to the underlying Restlet application instance. Here you can access some information defined in "settings.*", such as application.application.owner, application.application.author, application.application.statusService.contactEmail, etc.
You can configure the underlying application instance via "application.*".
application.logger
Use the logger to print messages in the log files. The messages are prefixed by the applicationLoggerName setting, which defaults to the application's subdirectory name.
By default, all log messages from all applications will be sent to prudence.log, but you can change this in /configuration/logging.conf.
application.getSubLogger
Uses a logger that inherits the application.logger configuration by default. The name you use will be appended to your base logger name with a ".".
application.getMediaType
Utility to get a Restlet MediaType instance from a MIME type name or filename extension.
Note that each application has its own mappings.
application.task
Asynchronously spawns or schedules in-process tasks from the application's /tasks/ subdirectory. See tasks for more information, and application.distributedTask for a distributed version of this API.
There are seven arguments:
  1. The application's full name, per the application setting, or null to run in this application.
  2. The document name in /tasks/.
  3. The entry point name (or null). If not specified, then the document in /tasks/ will be simply executed. If specified, then the document will be executed only once, and subsequent calls will involve calling the entry point names here. This may result in better performance for tasks that are called very often. See handlers for more information on how entry points work. Note that entry points are specified in camel case, and are converted to format appropriate for your flavor of Prudence. For example, an entry point named "handleTask" will be called as "handle_task" in Python and "handle-task" in Clojure.
  4. The context (or null). This is later available for the task as document.context.
  5. Initial delay in milliseconds, or zero for ASAP.
  6. Repeat delay in milliseconds, or zero for no repetition.
  7. Whether repetitions are at fixed times (true), or if the repeat delay begins when the task ends (false). This value matters only if the repeat delay is greater than zero. Note that "true" may cause a new instance of the task to be spawned again before the previous one completes.
For example, to schedule a task to run exactly very 10 minutes, starting now:
application.task(null, '/email/reminders/', null, null, 0, 10 * 60 * 1000, true)
Note that even if the task is set to run ASAP, with no repetition, it is executed asynchronously on a different thread. If you want to block until that task is completed, use the fact that this method returns a "future" object. Calling get() on this object will block until done. For example:
application.task(null, '/database/cleanup/', null, 'fast', 0, 0, false).get()
You can also store this "future" object, and check (without blocking) to see if the task was completed:
var cleanup = application.globals.get('myapp.task.cleanup')
if(cleanup && cleanup.done) {
	application.logger.info('Database already clean!')
} else {
	application.globals.put('myapp.task.cleanup', application.task(null, '/database/cleanup/', null, null, 0, 0, false))
}
application.distributedTask
Asynchronously spawns tasks on any Prudence instance which is part of the Prudence cluster to which we belong (may also be our current instance). Other than the fact that this task may run out-of-process, this API is similar to application.task.
There are six arguments, where the first four are identical to those in application.task:
  1. The application's full name, per the application setting, or null to run in this application.
  2. The document name in /tasks/.
  3. The entry point name (or null).
  4. The context (or null). Note that for distributed tasks, unlike in-process tasks, the context must be serializable. Depending on your object implementation, this may mean having to manually serialize/deserialize the context into a string or another serializable format.
  5. Where to run the task:
    1. Null: let Hazelcast decide
    2. Member instance: the particular cluster member
    3. String: the particular cluster member for this key
    4. Iterable of Member instances: run on one or all of these members (see the sixth argument, below)
  6. Multi-task support: whether the task should run on each member of the cluster (true), or on just one (false). Only applicable if "where" is an iterable of member instances.
See application.distributedGlobals for an easy way to share state with distributed tasks.

document

The "document" service has two distinct uses: first, it represents the file you are in: this document. This is where you can access the document's attributes and possibly change them. The second use of this service is for accessing other documents. Prudence combines these two uses into one service, but they functionally separate.
In the case of /resources/ and the configuration scripts, "this document" is simply the source code file. In the case /web/dynamic/, it's the whole "text-with-scriptlets" page, so it is shared by all scriptlets on the page, even if they are written in different languages.

This Document

Many of these attributes have to do with caching. Caching is your best tool to make sure your application can scale well. Read more about it in "Scaling Tips".
Note: Passing the "document" global variable as an argument to a function in another document will not let that other document access attributes for the calling document. In fact, all documents in the current thread share the same "document" service, which internally gets and sets attributes for the document in which it is accessed. If you do have a need for a function in one document to manipulate the "document" attributes of another document, you will have to return the values and have code in the owning document explicitly set the attributes there.
document.cacheDuration
(only available in /web/dynamic/ and /web/fragments/)
The duration in milleseconds for which the output of this document, along with all its response characteristics, will be cached. If this value is zero, the default, then caching is disabled. So, you must explicitly set this to a greater than zero value to enable caching. The key used to store and retrieve the cached output is determined by document.cacheKeyPattern.
See the section on caching in HTML generation for more information.
Note that if you edit the source file such as that it would be reloaded, within the limits defined by the minimumTimeBetweenValidityChecks setting, then any cache entries would not be used for the next request. This feature lets you see your changes on the fly, even if the document has been cached. Note, too, that updates to documents executed by your source file will also trigger this feature. See document.execute.
document.cacheKeyPattern
(only available in /web/dynamic/ and /web/fragments/)
This lets you control the key that is used to store and retrieve the cached output of the current document. Note that this is not necessarily the key itself, but instead a pattern that can contain variables that are set dynamically. See URI patterns for a complete list of built-in variables. You can additionally set your own variables using cache key pattern handlers.
The cache key itself is cast from the cache key pattern for every request reaching your document. This cache key is checked against the cache, and if a valid cache entry exists there, it is used. Otherwise, your document is executed. For debugging purposes, you can use document.cacheKey to output the actual cache key used for the request.
Whatever pattern you use, Prudence will always add conversation.encoding to the key, because you always want different encodings to be cached separately.
document.cacheKeyPattern is ignored if document.cacheDuration is zero.
Cache key patterns are a very powerful feature that lets you easily create different cached versions of documents for different kinds of users and requests, but it's not always trivial to determine the best cache key pattern for every situation. It depends strongly on how you use and cache your fragments.
The default cache key pattern is "{ri}|{dn}|{ptb}", which is a string containing the request identifier (the URI), the document name, the path to base. An actual key could thus be: "http://mysite.org/wackywiki/main|/common/header.html". This default is sensible, because it makes sure that included fragments are cached invidually. For example, only using "{ri}" would have each included fragment use the same key and override others.
However, though sensible, the default cache key pattern may not be the most efficient. For example, if the header fragment used in the example above never changes per page, then it's wasteful to cache it separately per URI. It would be more efficient to set document.cacheKeyPattern = "{an}|{dn}" or "{an}|{ptb}|{dn}" in a scriptlet in header.html.
Rule of thumb: Set document.cacheKeyPattern to be as short as you possibly can, but not too short that it won't differentiate between different views. See the section on caching in HTML generation and "Scaling Tips" for more information on how caching can help you scale.
The default cache key pattern can be changed by setting the "com.threecrickets.prudence.GeneratedTextResource.defaultCacheKeyPattern" application global.
document.cacheKeyPatternHandlers
A map of cache key pattern variable names (without the curly brackets) to cache key pattern handlers names. Use this to override global handlers, or to otherwise set handlers specific to this document.
See cache key pattern handlers for a complete reference.
document.cacheKey
This is the actual cache key: document.cacheKeyPattern, cast for the current conversation. The value is read-only, and made available mostly for debugging purposes.
document.cacheTags
(only available in /web/dynamic/ and /web/fragments/)
This is a list of one or more strings that will be attached to the cached output of the page. Any number of tags can be associated with a cache entry. Cache tags are used for document.cache.invalidate. Note that you can set cache tags to hardcoded strings (for example: "browse-pages") or dynamically generate them using code (for example: "blog-comments-for-entry-" + blogId).
document.cacheTags is ignored if document.cacheDuration is zero.
See "Scaling Tips" for more information on how cache tags can be used to help you scale.
Important! Using document.include, or the <%& .. %> inclusion scriptlet, will cause the cache tags of the included document to be applied to the current document, and to documents that have included the current document, and so on. This is a useful feature: if a fragment included somewhere down the line is invalidated, you likely want the containing document to also be invalidated. If you wish to disable cache tag propagation, prefix the cache tag with an underscore when adding it to document.cacheTags. The tag is not stored with the underscore, and so should be referenced without the underscore when calling document.cache.invalidate.
document.cache
(note that the same cache instance is accessible in both /web/dynamic/ and /resources/)
Provides access to the cache backend used by this document. Prudence supports a pluggable cache backend mechanism, allowing you to use RAM, disk, database, distributed and other cache systems. It also allows for chaining of various backends together for improved performance and reliability.
Though Prudence automatically caches the output of dynamic HTML and fragments, you can use the cache directly for your own purposes. Cache entries are instances of CacheEntry, which embeds various formatting attributes that you are free to ignore if you don't need them.
Prudence's default cache backend is a cache chain with a Hazelcast cache, but this can be configured differently. Here's an example of chaining a 1MB in-process memory cache before and a H2 database cache after the default cache, via the "component.*" configuration script:
(Why would you want to do this? The in-process memory caches ensure the fastest retrieval times, while the addition of the database cache makes sure entries will persist even if you restart Prudence.)
// Implement defaults
document.execute('/defaults/instance/component/')
​
// Create an H2-database-backed cache chained after the default caches
importClass(
	com.threecrickets.prudence.cache.InProcessMemoryCache
	com.threecrickets.prudence.cache.H2Cache)
​
var chainCache = component.context.attributes.get('com.threecrickets.prudence.cache')
​
// The constructor accepts a max size in bytes (defaults to 1 MB)
// (Note that you have to make sure that the JVM has been started with a heap
// size large enough for your cache)
chainCache.caches.add(0, new InProcessMemoryCache(10 * 1024 * 1024 * 1024)) // 10 MB
​
// H2 will store its database files in the supplied path
chainCache.caches.add(new H2Cache('cache/prudence/prudence'))
Prudence also comes with memcached and MongoDB cache backends, which can similarly be chained together. Here are simple examples:
(For more information about distributed cache backends, see Prudence clusters.)
importClass(
	com.threecrickets.prudence.cache.MemcachedCache,
	com.threecrickets.prudence.cache.HazelcastCache,
	com.threecrickets.prudence.cache.MongoDbCache)
​
// memcached: The constructor accepts a list of nodes (ports required)
component.context.attributes.put('com.threecrickets.prudence.cache',
	new MemcachedCache('127.0.0.1:11211, 192.168.1.12:11211'))
​
// Hazelcast: The configuration is in /configuration/hazelcast.conf
component.context.attributes.put('com.threecrickets.prudence.cache',
	new HazelcastCache())
​
// MongoDB
component.context.attributes.put('com.threecrickets.prudence.cache',
	new MongoDbCache())
document.cache.invalidate
This lets you remove entries, zero or many, from your cache at once. It is useful for when your application state changes in such a way that certain pages must be regenerated. The argument is a cache tag, as defined by document.cacheTags.
A common use case is to invalidate display pages when a user posts new data. For a detailed example, consider a forum hosting site. The home page has a section showing "recent posts in our forums" and additionally each forum has its own front page showing "forum highlights". Both of these query the data backend in order to generate the last, and have a 24-hour cache. You can associate each forum with cache tag "forum-X", where X is the forum number, and associate the home page with all these cache tags. When a user posts a new forum thread in forum X, you just need to call document.cache.invalidate("forum-X") to make sure all associated pages will be regenerated on the next user request.
See "Scaling Tips" for more information on how caching can help you scale.
document.source
This provides access to the underlying Scripturian DocumentSource instance used for this document.
Scripturian is Prudence's mechanism for loading, caching and executing source code in various languages. By default, Prudence uses a FileDocumentSource. From here, you can access attributes of it, for example: document.source.basePath and document.source.minimumTimeBetweenValidityChecks.
Refer to Scripturian's documentation for a complete reference.
document.invalidateCurrent
Forces the current document to be marked as invalid, which would trigger the current and dependent documents to be refreshed. See document.addDependency and document.invalidate.
document.context
(only available in /tasks/)
Access to the context optionally sent to the task. See application.task and application.distributedTask.

Other Documents

document.include
(only available in /web/dynamic/ and /web/fragments/)
Executes another document "in place," meaning that its output is appended at the location in the document where you call document.include. Global variables, function definitions, class definitions, etc., in the other document would be made available locally.
The included path is an internal URI, not the external URL visible to the world. The URI can be relative to either /web/dynamic/ or to /web/fragments/.
Prudence might compile or otherwise prepare and cache scriptlets in the included document. What "preparation" actually involves depends on the language of the source code. This means that the first time it is included it would be delayed, but subsequent includes would be much faster. To avoid that first-time wait, Prudence supports "defrosting" of your documents when it starts. This is enabled by default for all documents in /web/dynamic/, and would affect included documents from /web/fragments/.
Calling document.include is equivalent to using the include scriptlet, <%& … %>. Internally, the include scriptlet is turned into a regular scriptlet that calls document.include.
There are three main use cases for inclusion:
  1. This mechanism allows you to divide your documents into fragments that you can re-use in many documents, helping you manage large applications and keep them consistent. Fragments can include other fragments, those can include others, etc. A common strategy is to separate the document header and footer into fragments and include these in all pages.
  2. Because each document fragment can have its own caching properties, fragmentation is also an important strategy for fine-grained caching. It's important to keep in mind, though, that the outermost document's cacheDuration will override all others. If a cached version of a document is used, then it is not executed, which means that document.include calls in it are not executed, too.
  3. Scriptlets in the fragments can include re-usable code, such as function and class definitions. You can thus use document.include to include code libraries. You might want to consider, though, using document.execute instead, as it will let you use regular source code documents and not have to use scriptlets.
document.execute
Executes a program defined by source code in another document. Global variables, function definitions, class definitions, etc., in the other document would be made available locally. See document.executeOnce for a useful variant.
The included path is an internal URI, not the external URL visible to the world. The URI can be relative to either /web/dynamic/ in the case of scriptlets, /resources/ in the case of resources, or to /libraries/ in either case. The /libraries/ subdirectory is indeed the best place to put code usable by all parts of your application.
Prudence might compile or otherwise prepare and cache code in the executed document. What "preparation" actually involves depends on the language of the source code. This means that the first time the document is executed would likely be slower than normal, due to the preparation, but subsequent execution would be much faster. To avoid that first-time wait, Prudence supports "defrosting" of your documents when it starts. This is enabled by default for all documents in /resources/, and would affect documents they execute from /libraries/.
Executed documents, just like documents exposed at URLs, are reloaded and re-prepared on the fly if the file changes, within the limits of the minimumTimeBetweenValidityChecks setting. Also, executed documents are automatically added as dependencies, so that if they are reloaded, so will the current document. See document.addDependency.
The main difference between document.include and document.execute is that the former expects "text-with-scriptlets" documents, while the latter uses plain source code.
Common use cases:
  1. The executed code can be re-usable, such as function and class definitions. This allows you to treat it as a code library. Notes:
    1. For JavaScript and Groovy flavors: Prudence's document.execute is your only option for code inclusion in Prudence, because both JavaScript and Groovy do not have a code inclusion mechanism. Note that in many of these cases you are treating the code as a library, and are better off using document.executeOnce instead.
    2. In most cases, you would probably want to use your language's code inclusion mechanism instead of document.execute. For example, use "import" in Python, Ruby and PHP, and "require" in Clojure. The native inclusion mechanism might do a better job at caching code, managing namespaces, avoiding duplication, etc.
  2. The executed code does not have to be in the same language as the calling code. This lets you use multiple languages in your applications, using the strengths of each. Note that languages cannot normally share function and class definitions, but can share state, via mechanisms such as application.globals, if the languages use compatible structures.
  3. Use document.execute as an alternative to document.include if you prefer not to use scriptlets. For example, executing "mylibrary.js" might be more readable than including a fragment called mylibrary.html, if it is all just one JavaScript scriptlet.
document.executeOnce
This is identical to document.execute, except that it does nothing if the document was already called with document.executeOnce for this thread. It is thus similar to "require" or "import" functionality in many programming languages.
The use case is for documents that constitute libraries: functions, classes and utilities that need only be added once to the global space. Using document.executeOnce instead of document.execute would allow for greater efficiency and performance if you have a complex tree of documents that execute each other.
See document.markExecuted for a way to control this mechanism explicitly.
document.markExecuted
Explicitly marks a document as executed or not-executed, thus controlling the result of subsequent calls to document.executeOnce. The first argument is the document name, the second is a boolean for the execution flag.
document.addDependency
Explicitly adds a dependency to this document. The dependency is an internal document URI, identical to what would be used in document.execute.
If the dependency changes, the current document would be reloaded when accessed. This effect cascades to documents that the dependency depends on. This is useful, because the execution document may contain code that affects the preparation of the current document.
Note that document.execute automatically adds the executed document as dependency. You would want to call document.addDependency explicitly only if you are not using document.execute. For example, if you are using your language's built-in library inclusion mechanisms.
document.addFileDependency
Like document.addDependency, except that any file path can be added as a dependency. The contents of such files is ignored.
For constructing the file path, you may want to use document.source.basePath.
document.invalidate
Forces the provided document to be marked as invalid, which would trigger the document and dependent documents to be refreshed. See document.addDependency and document.invalidateCurrent.
document.internal
Creates a ClientResource proxy for a resource in the current application, or in other applications in your component. You can perform all the usual REST verbs via the proxy: GET, PUT, POST, DELETE, etc.
The first argument is a URI, which is not relative to any server or virtual host, but to the application root. The second argument is the preferred media type name.
Common use cases:
  1. By letting you use your REST resources both internally and externally, document.internal lets you create unified resource API. document.internal directly accesses the resource (not via HTTP), so it is just about as fast as a simple function call, and is definitely scalable. Thus, there may be no need to create a separate set of functions for you to use internally and HTTP resources for external clients to use. A unified API would minimize the possibility of bugs and add coherence to your code, by enforcing a RESTful architecture all around. A second advantage is that you could trivially make your API remote by running it on a different Prudence instance, and using document.external instead. This could allow for an easy way to run your application in a cluster, behind a load balancer.
  2. You can create unit tests for your resources without having to start an HTTP server.
  3. The preheating mechanism uses document.internal to load resources.
document.internal makes requests via the RIAP (Restlet Internal Access Protocol). You can construct URIs of this pseudo-protocol on your own, like so: "riap://application/myresource/". A call to document.external with this URI would be identical to calling document.internal with "/myresource/". See the Restlet documentation for full information about RIAP and other pseudo-protocols (CLAP, JAR, WAR, etc.).
document.external
Creates a ClientResource proxy for a resource. You can perform all the usual REST verbs via the proxy: GET, PUT, POST, DELETE, etc.
The first argument is a full URL. The second argument is the preferred media type name.
Common use cases:
  1. You can add scale and redundancy to your internal REST API by running several Prudence instances behind a load balancer, and using document.external instead of document.internal.
  2. You can use Prudence to create an internal communication backbone for your enterprise, with various backend services exposing resources to each other. You can expose the same resources to business partners, allowing for "B2B" (business-to-business) services.
  3. There are many, many use cases for document.external, and they keep growing as REST is adopted by service providers on the Internet. There are online storage, publishing and content delivery systems, public databases, archives, geolocation services, social networking applications, etc. Perhaps, with Prudence, you will create the next one.
Important: The semantics of ClientResource require you to explicitly release the response as soon as you're done using it, which will in turn release the thread used to stream the response to you. (Though these threads might eventually be reclaimed by a built-in garbage collection mechanism, leaving them hanging could lead to starvation, such that further uses of document.external will block until threads return to the pool.) It's probably best to employ a try-finally paradigm, where the try clause contains as little code as possible, and the finally clause releases the response. See the example below.
This is a good place to remind you that REST is not just HTTP. By default, Prudence supports http: and file: scheme URIs for document.external, and you can add more protocols via your "/instance/clients.*" configuration script.
Here's an example of using document.external to read a JSON representation from the filesystem:
var fixture
var resource = document.external('file:///myfiles/fixture.json', 'application/json')
try {
	fixture = resource.get().text
}
finally {
	resource.response.release()
}
​
// Do something with the fixture here...
In case you're interested, Prudence internally uses Apache's HTTP client for high-performance access to external HTTP documents, with full support for secure connections (prefixed with "https://").
document.preferredExtension
(only available in configuration scripts)
If multiple files with the same name but different extension exist in the same directory, then this extension will be preferred.
This value is set automatically according the Prudence flavor you are using.

executable

The "executable" is the low-level equivalent of "this document". Here you can explore which languages are installed in your Prudence instance, and gain access to their implementation mechanism. You'll likely never need to do any of this in your Prudence application. For more information on executables, see Scripturian, the library that handles code execution for Prudence.
A feature that you might want to use here is the executable globals. These are similar to the application globals, except that they are global to the entire Prudence instance (in fact, to the virtual machine). It's a good place to store state that you want shared between Prudence applications.
executable.globals, executable.getGlobal
These are similar in use to application.sharedGlobals, but may also be accessed by non-Prudence Scripturian code running on the same VM. See "sharing state" for more information.
executable.context
The context is used for communication between the Prudence container and the executable.
  • executable.context.writer: direct access to the output writer (writes to a memory buffer in /web/dynamic/, and to standard output in /resources/, /handlers/, /tasks/ and configuration scripts)
  • executable.context.exposedVariables: Prudence services are here (application, document, executable, conversation)
  • executable.context.attributes: for use by the language engines
executable.manager
This is the Scripturian LanguageManager used to create executables in many languages. Here you can query which languages are supported by the current Prudence instance: executable.manager.adapters.
executable.container
This is identical to the "document" service. For example, "document.include" is the same as "executable.container.include".
(Internally, Prudence uses this equivalence to hook the include scriptlet, a Scripturian feature, into Prudence's document.include.)

conversation

The "conversation" represents the request received from the user as well your response to it, hence it's a "conversation." Because Prudence is RESTful, conversations encapsulate exactly a single request and its response. Higher level "session" management, covering an arbitrary number of conversations, is up to you.
Here you can access various aspects of the request: the URI, formatting preferences, client information, and actual data sent with the request (the "entity"). You can likewise set response characteristics.
Note that in /web/dynamic/ "conversation" is available as a global variable. In /resources/ and /handlers/, it is sent to the handling entry points (functions, closures, etc.) as an argument. Usage is otherwise identical. "conversation" is not available in configuration scripts or in /tasks/.

The Request

conversation.reference
This is the URI used by the client. Refer to the Restlet Reference documentation for complete details.
A few useful attributes:
  • conversation.reference.identifier: the complete URI
  • conversation.reference.path: the URI, not including the domain name and the query matrix
  • conversation.reference.segments: a list of URI segments in the path
  • conversation.reference.lastSegment: the last segment in the URI path
  • conversation.reference.fragment: the URI fragment (whatever follows "#")
  • conversation.reference.query: the URI query (whatever follows "?"); you might prefer to use conversation.query instead
  • conversation.reference.relativeRef: a new reference relative to the base URI (usually the application root URI on the current virtual host)
conversation.pathToBase
This is a URI path relative to the base URI, which is usually the application root URI on the current virtual host.
This is very useful to allow for relative references in HTML. It's especially sueful in fragments that you might include at various parts of your application, and for captured URIs. For example, let's say you have a contact page at /dynamic/web/contact/index.html. The following HTML snippet can be used anywhere in your application:
Click <a href="<%= conversation.pathToBase %>/contact/">here</a> to contact us.
Note: If you are caching a document that relies on conversation.pathToBase, you likely also want to include "{ptb}" or some other path reference in the document.cacheKeyPattern.
conversation.form, conversation.formAll
Use these to access data sent via HTML forms, or any other request with a data entity of the "actual" MIME type.
conversation.form is a map of form names to values. In case the form has multiple values for the same name, only the last one is mapped.
conversation.formAll is a list of Restlet Parameter instances. Use this if you need to support multiple values for the same name. The most useful attributes are "name" and "value".
A value is often a simple string, but in the case of file uploads, it could be a FileParameter instance, with the following attributes:
  • data: the raw data as an array of bytes, for uploaded files stored in memory
  • file: a JVM File instance, for files stored on disk; uploaded files are stored in the application's /uploads/ subdirectory
  • size: in bytes
  • mediaTypeName: the MIME type set by the client, such as "image/jpeg"
  • mediaType: the MediaType instance equivalent to the above, if available
Note that either "data" or "file" are valid, but not both. One will always be null.
Prudence allows you to keep smaller files in memory. See the fileUploadSizeThreshold setting.
conversation.query, conversation.queryAll
Use these to access data sent via the URI query.
conversation.query is a map of query parameter names to values. In case the URI has multiple values for the same name, only the last one is mapped.
conversation.queryAll is a list of Restlet Parameter instances. Use this if you need to support multiple values for the same name. The most useful attributes are "name" and "value".
conversation.entity
(only available in HTTP PUT and POST handling)
This is the data entity sent by the client.
In the case of an HTML POST form, it would be more convenient to use conversation.form to access the parsed entity data. For other kinds of data, you have to parse the entity data yourself. Before attempting to parse entity data on your own, make sure to look through the Restlet API and its extensive set of plugins for tools to help you parse representations. Plugins exist for many common Internet formats.
The value is a Restlet Representation instance. A few useful attributes of this:
  • conversation.entity.size: the size of the data in bytes, or -1 if unknown
  • * conversation.entity.text: the data as text (only useful if the data is textual)
  • * conversation.entity.reader: an open JVM Reader to the data (only useful if the data is textual)
  • * conversation.entity.stream: an open JVM InputStream to the data (useful for binary data)
Note: Client data is provided as a stream that can only be "consumed" once. Attributes that cause consumption are marked with a "*" above. Note that conversation.entity.text is one of them! If you want to access conversation.entity.text more than once, save it to a variable first.
conversation.preferences
Provides you with low-level access to the Restlet Variant instance negotiated with the client. You will usually not have to use this, because response attributes are set accordingly.
Negotiation happens between the client's preferences, if provided by the client in the request, and our list of supported variations.
In /resources/, this attribute will be null during the handleInit entry point, precisely because we have not yet set our list of supported variations.
In /web/dynamic/ (and /web/static/), our supported media type will be set according to the filename extension.

The Response

By default, Prudence initializes all response attributes according to the client's preferences. However, you may want to explicitly change them. Also note that some clients to not specify preferences, in which case the response attributes will be undefined.
conversation.statusCode, conversation.status
This is the HTTP status code returned to the client. The default status code is 200 ("OK").
Prudence will automatically set the status code to 500 ("internal server error") in the case of an unhandled exception in your code. It can also display a debug page in such cases. You can also capture error codes and display custom error pages to users.
These two variants represent the same value, letting you access the value in different ways.
conversation.statusCode is an HTTP status code as an integer.
conversation.status is the underlying Status instance.
conversation.mediaTypeName, conversation.mediaTypeExtension, conversation.mediaType
In /resources/, the "application/java" media type is treated specially.
These three variants all represent the same value, letting you access the value in different ways.
conversation.mediaTypeName is the MIME representing the media type. MIME (Multipurpose Internet Mail Extensions) is an established web standard for specifying media types. Examples include: "text/plain," "text/html," "application/json," and "application/x-www-form-urlencoded." The exact list of supported MIME types depends on the underlying Restlet implementation.
conversation.mediaTypeExtension is the media type as the default filename extension for the media type. For example, "txt" is equivalent to MIME "text/plain," and "xml" is equivalent to "application/xml." Each application has its own mappings of filename extensions to media types. See also application.getMediaType, and how to change the mappings for your application.
conversation.mediaType is the underlying MediaType instance.
The default media type will be set according to conversation.preferences. Use conversation.request.clientInfo.acceptedMediaTypes to find out more generally which media types the client supports.
conversation.characterSetName, conversation.characterSetShortName, conversation.characterSet
These three variants all represent the same value, letting you access the value in different ways.
conversation.characterSetName is ISO's UTC (Universal Character Set) name of the character set. For example, "ISO-8859-1" is the "Latin 1" character set and "UTF-8" is the 8-bit Unicode Transformation Format, "US-ASCII" is ASCII, etc. The exact list of supported ISO names depends on the underlying Restlet implementation.
conversation.characterSetShortName is a shortcut name for the character set. Shortcuts include "ascii," "utf8," and "win" (for the Windows 1252 character set). Restlet handles shortcuts names together with filename extension mappings..
conversation.characterSet is the underlying CharacterSet instance.
The default character set will be set according to conversation.preferences. Use conversation.request.clientInfo.acceptedCharacterSets to find out more generally which character sets the client supports.
If the client does not specify a preferred character set, then the character set will fall back to a default determined by the "com.threecrickets.prudence.GeneratedTextResource.defaultCharacterSet" application global. It not explicitly set, it will be UTF-8.
conversation.languageName, conversation.language
These two variants represent the same value, letting you access the value in different ways.
conversation.languageName is the IETF locale name for the language. Examples include "en" for English, "en-us" for USA English, "fr" for French, etc. The exact list of supported IETF names depends on the underlying Restlet implementation.
conversation.language is the underlying Language instance. Because IETF names are hierarchical, you might prefer to use this as a way to test for containment. For example, conversation.language.includes will tell you that "en-us" is included in "en."
The default language will be set according to conversation.preferences. Use conversation.request.clientInfo.acceptedLanguages to find out more generally which languages the client supports.
Note that the language can be a null value, and that responses do not have to specify a language.
conversation.encodingName, conversation.encoding
In /web/dynamic/, this value is read-only, defined by client. If it is "zip", "gzip" or "deflate," Prudence will compress the output text. Cache entries are stored in their compressed state, such that subsequent cache retrievals will not involve this additional overhead. See caching and encoding for a detailed discussion
In /resources/, you can change this value, but all it would do is set the appropriate header in the response. It is up to you to supply Prudence with a correctly encoded representation.
These two variants represent the same value, letting you access the value in different ways.
conversation.encodingName is an internal name used for the encoding. Examples include "zip," "gzip," "compress," and "deflate." The "*" name represents all possible encodings. The exact list of supported names depends on the underlying Restlet implementation.
conversation.encoding is the underlying Encoding instance.
The default encoding will be set according to conversation.preferences. Use conversation.request.clientInfo.acceptedEncodings to find out more generally which encodings the client supports.
conversation.disposition
Access to the response disposition, which lets you define how clients should treat your representation.
conversation.disposition.type can accept the values "attachment", "inline" or "none".
Example:
conversation.disposition.type = 'attachment'
conversation.disposition.filename = 'revenue.csv'
conversation.headers
Access to the response headers as a form.
Note that these are extra headers in addition to those set automatically by Prudence, and that you cannot use this API to override the automatic headers. Also note that these extra headers are cached together with the rest of the response characteristics: see document.cacheDuration.
Example:
conversation.headers.add('X-Pingback', 'http://mysite.org/pingback/')

Cookies

conversation.cookies
This is initialized as a list (not a map) of cookies sent from the client. Use conversation.getCookie as a shortcut to retrieve a cookie by name.
If you want to ask the client to change any of them, be sure to call save() on the cookie in order to send it in the response. You can also call remove() to ask the client to delete the cookie (no need to call save in that case; internally sets maxAge to zero). Note that you can call save() and remove() as many times as you like, and that only the last changes will be sent in the response.
Note that you can only ask a client to change, store cookies, or for them to be used in various. It's up to the client to decide what to do with your requirements. For example, many web browsers allow users to turn off cookie support or filter out certain cookies.
Cookie instances have the following attributes:
  • cookie.name: (read only)
  • cookie.version: (integer) per a specific cookie.name
  • cookie.value: textual, or text-encoded binary data (note that most clients have strict limits on how much total data is allowed to be stored in all cookies per domain)
  • cookie.domain: the client should only use the cookie with this domain and its subdomains (web browsers will not let you set a cookie for a domain which is not the domain of the request or a subdomain of it)
  • cookie.path: the client should only use the cookie with URIs that begin with this path ("/" would mean to use it with all URIs)
The following attributes are not received from the client, but you can set them for sending to the client:
  • cookie.maxAge: age in seconds, after which the client should delete the cookie. maxAge=0 deletes the cookie immediately, while maxAge=-1 (the default) asks the client to keep the cookie only for the duration of the "session" (this is defined by the client; for most web browsers this means that the cookie will be deleted when the browser is closed).
  • cookie.secure: true if the cookie is meant to be used only in secure connections (defaults to false)
  • cookie.accessRestricted: true if the cookie is meant to be used only in authenticated connections (defaults to false)
  • cookie.comment: some clients store this, some discard it
conversation.getCookie
Gets a cookie by its name, or returns null if it doesn't exist. See conversation.cookies.
conversation.createCookie
You must provide the cookie name as an argument. Returns a new cookie instance if the cookie doesn't exist yet, or the existing cookie if it does. The cookie is added to conversation.cookies.
For new cookies, be sure to call save() on the cookie in order to send it in the response, thus asking the client to create it, or delete() if you want to cancel the creation (in which case nothing will be sent in the response).

Conditional Requests

These attributes are only available for /resources/. See conditional requests for use cases.
For /web/dynamic/, these attributes are indirectly set according to document.cacheDuration and dynamicWebClientCachingMode setting.
conversation.modificationTimestamp, conversation.modificationDate
These two variants represent the same value, letting you access the value in different ways.
conversation.modificationTimestamp is a long integer value representing the number of milliseconds since January 1, 1970, 00:00:00 GMT ("Unix time"). Once you set conversation.modificationTimestamp for a conversation, you cannot "unset" it—you can only change it to another value. You can, however, set conversation.modificationDate to null instead.
conversation.modificationDate is the underlying JVM Date instance. Refer to the Java API documentation for details.
conversation.httpTag, conversation.tag
These two variants represent the same value, letting you access the value in different ways.
conversation.httpTag is an HTTP ETag string. Once you set conversation.httpTag for a conversation, you cannot "unset" it—you can only change it to another value. You can, however, set conversation.tag to null instead.
conversation.tag is the underlying Tag instance.

Client Caching

These attributes are only available for /resources/. See conditional requests and client caching for use cases.
For /web/dynamic/, these attributes are indirectly set according to document.cacheDuration and dynamicWebClientCachingMode setting.
conversation.maxAge
Clients are asked not to use cached versions of the response entity for more than this number of seconds and not to perform conditional requests until then. You can also use conversation.expirationDate instead of this value (for most clients, this value supercedes conversation.expirationDate).
Unlike most other attributes, once you set the max age for the conversation, you cannot "unset" it—you can only change it to another value.
A value of -1 is special: it signifies that Prudence should use the "no-cache" HTTP directive instead of "max-age". Though it make look as if it would have the same effect as setting "max-age" to zero, some clients interpret "no-cache" more explicitly and make sure not to keep any local copy of the response.
Cache expiration can be used either alone or in conjunction with conditional requests. See the dynamicWebClientCachingMode setting for further discussion.
conversation.expirationTimestamp, conversation.expirationDate
Clients are asked not to use cached versions of the response entity after this date and not to perform conditional requests until then. You can also use conversation.maxAge instead of this value (for most clients, conversation.maxAge supercedes this value).
Cache expiration can be used either alone or in conjunction with conditional requests. See the dynamicWebClientCachingMode setting for further discussion.
These two variants represent the same value, letting you access the value in different ways.
conversation.expirationTimestamp is a long integer value representing the number of milliseconds since January 1, 1970, 00:00:00 GMT ("Unix time"). Once you set conversation.expirationTimestamp for a conversation, you cannot "unset" it—you can only change it to another value. You can, however, set conversation.expirationDate to null instead.
conversation.expirationDate is the underlying JVM Date instance. Refer to the Java API documentation for details.

Resource Initialization

conversation.addMediaTypeByName, conversation.addMediaTypeByExtension, conversation.addMediaType
These three variants all do the same thing, letting you add a media type to the list of media types you can support in the response. After negotiation with the client's preferences, the preferred media type will be found in conversation.preferences and in conversation.mediaType.
You'll want to call one or more of these in a resource's handleInit() entry point. Order of these calls is important, as it defines order of preference in case the client supports multiple media types.
conversation.addMediaTypeByName accepts the MIME representing the media type. MIME (Multipurpose Internet Mail Extensions) is an established web standard for specifying media types. Examples include: "text/plain," "text/html," "application/json," and "application/x-www-form-urlencoded." The exact list of supported MIME types depends on the underlying Restlet implementation.
conversation.addMediaTypeByExtension accept the media type as the default filename extension for the media type. For example, "txt" is equivalent to MIME "text/plain," and "xml" is equivalent to "application/xml." Each application has its own mappings of filename extensions to media types. See also application.getMediaType, and how to change the mappings for your application.
conversation.addMediaType accepts the underlying MediaType instance.

Conversation Flow

conversation.stop
Throws an exception, thereby ending execution of your code, and hence the conversation—unless you have deferred it: see conversation.defer. Note that the client will still get a response, so you can set attributes (conversation.statusCode, conversation.expirationTimestamp, etc.) before calling conversation.stop.
conversation.internal
True if the client's request was internal, false if it was external.
Internal requests are usually created in one of two ways:
  1. The document.internal API
  2. URI capturing
conversation.locals
A map of general purpose attributes that is destroyed at the end of the conversation. Importantly, conversation.locals are maintained even if the conversation has been deferred via conversation.defer.
See "sharing state" for more information.
conversation.defer
(only available in /web/dynamic/ and /web/fragments/)
Releases the current conversation thread, and queues handling of this conversation on a separate thread pool. When the conversation turn comes to be handled, it will cause the page to be executed again, but with conversation.deferred set to true. Use conversation.locals if you want to pass state for the deferred execution.
Returns true if indeed the conversation has been successfully deferred. Will return false if the conversation is already deferred.
Note that calling conversation.defer does not stop the current execution. You'd likely follow a successful call to conversation.defer with a call to conversation.stop. For example:
if(conversation.defer()) {
	conversation.stop()
}
This is an experimental feature in Prudence 1.1. The use of a separate thread pool is only supported when using the internal Restlet connector. For other connectors (such as Jetty, the default), a successful call to defer will cause the page to be executed again in the same thread.
conversation.deferred
(only available in /web/dynamic/ and /web/fragments/)
True if the conversation has been deferred via a call to conversation.defer.

Low-level Access

Use these to access the Restlet instances underlying the conversation. This is useful for features not covered by Prudence's standard API.
For more information, refer to Prudence's Java API documentation and also Restlet's API documentation.
conversation.resource
The Restlet resource instance. In the case of /web/dynamic/, this will be Prudence's GeneratedTextResource. In the case of /resources/, this will be DelegatedResource.
conversation.request
conversation.response

Sharing State

Prudence is designed to allow massive concurrency and scalability while at the same time shielding you from the gorier details. However, when it comes to sharing state between different parts of your code, it's critical that you understand Prudence's state services.

conversation.locals

These are not "local" in the same way that code scope locals are. The term "local" here should be read as "local to the conversation." They are "global" in the sense that they can be accessed by any function in your code, but are "local" in the sense that they persist only for the duration of the user request. (Compare with "thread locals" in the JVM, which are also "local" in a specific sense.)
You may ask, then, why you wouldn't want to just use your language globals, which have similar scope and life. conversation.locals have three main uses in Prudence:
  1. To easily share conversation-scope state between scriptlets written in different languages.
  2. To share state for deferred conversations—see conversation.defer. In such cases, your language's globals would not persist across the thread boundaries.
  3. They are Prudence's general mechanism for sending state to your code in a conversation. For example, captured URI segments are stored here as well as document.cacheKeyPattern variables.

Global Variables

You know how local variables work in your programming language: they exist only for the duration of a function call, after which their state is discarded. If you want state to persist beyond the function call, you use a global variable (or a "static" local, which is really a global).
But in Prudence, you cannot expect global variables to persist beyond a user request. To put it another way, you should consider every single user request as a separate "program" with its own global state. See the "life" sections for generating HTML and resources for more information on when this global state is created and discarded. If you need global variables to persist, you must use application.globals, application.sharedGlobals or even application.distributedGlobals.
Why does Prudence discard your language's globals? This has to do with allowing for concurrency while shielding you from the complexity of having to guarantee the thread-safety of your code. By making each user request a separate "program," you don't have to worry about overlapping shared state, coordinating thread access, etc., for every use of a variable.
The exception to this is code in /resources/, in which language globals might persist. To improve performance, Prudence caches the global context for these in memory, with the side effect that your language globals persist beyond a single user request. For various reasons, however, Prudence may reset this global context. You should not rely on this side effect, and instead always use application.globals.

application.globals vs. application.sharedGlobals

The rule of thumb is to always prefer to use application.globals. By doing so, you'll minimize interdependencies between applications, and help make each application deployable on its own.
Use for application.sharedGlobals (and possibly application.distributedGlobals—similar concerns apply to it) only when you explicitly need a bridge between applications. Examples:
  1. To save resources. For example, if an application detects that a database connection has already been opened by another application in the Prudence instance, and stored in application.sharedGlobals, then it could use that connection rather than create a new one. This would only work, of course, if a few applications share the same database, which is common in many deployments.
  2. To send messages between applications. This would be necessary if operations in one application could affect another. For example, you could place a task queue in application.sharedGlobals, where applications could queue required operations. A thread in another application would consume these and act accordingly. Of course, you will have to plan for asynchronous behavior, and especially allow for failure. What happens if the consumer application is down? It may make more sense in these cases to use a persistent storage, such as a database, for the queue.
Generally, if you find yourself having to rely on application.sharedGlobals, ask yourself if your code would be better off encapsulated as a single application. Remember that Prudence has powerful URL routing, support for virtual hosting, etc., letting you easily have one application work in several sites simultaneously.
Note for Clojure flavor: All Clojure vars are VM-wide globals equivalent in scope to executable.globals. You usually work with namespaces that Prudence creates on the fly, so they do not persist beyond the execution. However, if you explicitly define a name space, then you can use it as a place for shared state. It will then be up to you to make sure that your namespace doesn't collide with that of another application installed in the Prudence instance. Though this approach might seem to break our rule of thumb here, of preferring application.globals to application.sharedGlobals, it is more idiomatic to Clojure and Lisps more generally.

application.sharedGlobals vs. executable.globals

executable.globals are in practice identical to application.sharedGlobals. The latter is simply reserved for Prudence applications. If you are running non-Prudence Scripturian code on the same VM, and need to share state with Prudence, then executable.globals are available for you.

Concurrency

Though application.globals, application.sharedGlobals, application.distributedGlobals and executable.globals are all thread safe, it's important to understand how to use them properly.
Note for Clojure flavor: Though Clojure goes a long way towards simplifying concurrent programming, it does not solve the problem of concurrent access to global state. You still need to read this section!
For example, this code (Python flavor) is broken:
def get_connection()
	data_source = application.globals['myapp.data.source']
	if data_source is None:
		data_source = data_source_factory.create()
		application.globals['myapp.data.source'] = data_source
	return data_source.get_connection()
The problem is that in the short interval between comparing the value in the "if" statement and setting the global value in the "then" statement, another thread may have already set the value. Thus, the "data_source" instance you are referring to in the current thread would be different from the "myapp.data.source" global used by other threads. The value is not truly shared! In some cases, this would only result in a few extra, unnecessary resources being created. But in some cases, when you rely on the uniqueness of the global, this can lead to subtle bugs.
This may seem like a very rare occurrence to you: another thread would have to set the value exactly between our comparison and our set. If your application has many concurrent users, and your machine has many CPU cores, it can actually happen quite frequently. And, even if rare, your application has a chance of breaking if just two users use it at the same time. This is not a problem you can gloss over, even for simple applications.
Use this code instead:
def get_connection()
	data_source = application.globals['myapp.data.source']
	if data_source is None:
		data_source = data_source_factory.create()
		data_source = application.getGlobal('myapp.data.source', data_source)
	return data_source.get_connection()
The getGlobal call is an atomic compare-and-set operation. It guarantees that the returned value is the unique one.
Optimizing for Performance
You may have noticed, in the code above, that if another thread had already set the global value, then our created data source would be discarded. If data source creation is heavy and slow, then this could affect our performance. The only way to guarantee that this would not happen would be to make the entire operation atomic, by synchronizing it with a lock:
Here's an example:
def get_connection()
	lock = application.getGlobal('myapp.data.source.lock', RLock())
	lock.acquire()
	try:
		data_source = application.globals['myapp.data.source']
		if data_source is None:
			data_source = data_source_factory.create()
			application.globals['myapp.data.source'] = data_source
		return data_source.get_connection()
	finally:
		lock.release()
Note that we have to store our RLock as a unique global, too.
Not only is the code above complicated, but synchronization has its own performance penalties, which might make this apparent optimization actually perform worse. It's definitely not a good idea to blindly apply this technique: attempt it only if you are experiencing a problem with resource use or performance, and then make sure that you're not making things worse with synchronization.
Here's a final version of our get_connection function that lets you control whether to lock access. This can help you more easily compare which technique works better for your application:
def get_connection(lock_access=False)
	if lock_access:
		lock = application.getGlobal('myapp.data.source.lock', RLock())
		lock.acquire()
	try:
		data_source = application.globals['myapp.data.source']
		if data_source is None:
			data_source = data_source_factory.create()
			if lock_access:
				application.globals['myapp.data.source'] = data_source
			else:
				data_source = application.getGlobal('myapp.data.source', data_source)
		return data_source.get_connection()
	finally:
		if lock_access:
			lock.release()
Complicated, isn't it? Unfortunately, complicated code and fine-tuning is the price you must pay in order to support concurrent access, which is the key to Prudence's scalability.
But, don't be discouraged. The standard protocol for using Prudence's globals will likely be good enough for the vast majority of your state-sharing needs.