Hosted by Three Crickets

Prudence
Scalable REST Platform
For the JVM

Prudence logo: bullfinch in flight

Configuring Applications

Prudence applications live in their own subdirectory under "/component/applications/". The subdirectory name itself can be considered a setting, as it is used as a default identifier for the application in various use cases.
Prudence uses "configuration-by-script" almost everywhere: configuration files are true JavaScript source code, meaning that you can do pretty much anything you need during the bootstrap process, allowing for dynamic configurations that adjust to their deployed environments.
Prudence, as of version 2.0, does not support live re-configuring of applications. You must restart Prudence in order for changed settings to take hold. The one exception is crontab: changes there are picked up on-the-fly once per minute.

Overview

The subdirectory contains five main configuration files:

settings.js

If you use the default template with the "sincerity prudence create" command, you should get a setting.js file that looks something like this:
app.settings = {
	description: {
		name: 'myapp',
		description: 'Skeleton for myapp application',
		author: 'The Author',
		owner: 'The Project'
	},
	errors: {
		debug: true,
		homeUrl: 'http://threecrickets.com/prudence/',
		contactEmail: 'info@threecrickets.com'
	},
	code: {
		libraries: ['libraries'],
		defrost: true,
		minimumTimeBetweenValidityChecks: '1s',
		defaultDocumentName: 'default',
		defaultExtension: 'js',
		defaultLanguageTag: 'javascript',
		sourceViewable: true
	},
	templates: {
		debug: true
	},
	caching: {
		debug: true
	},
	uploads: {
		root: 'uploads',
		sizeThreshold: '0kb'
	},
	mediaTypes: {
		php: 'text/html'
	},
	logger: 'myapp'
} 

Numeric Shortcuts

Time durations are in milliseconds and data sizes in bytes. But these can be specified as either numbers or strings:
You can accomplish the same trick for your own code using Sincerity.Localization.toMilliseconds and Sincerity.Localization.toBytes.

app.settings.description

Information here is meant for humans.
This information appears in the Prudence Administration application, and you may access it yourself using the application.application.name, application.application.description, etc., APIs.

app.settings.errors

Configure Prudence's error handling behavior here.
Note that you can route your own error pages in routing.js to replace the default template. If you wish to use the values you set here in your template, use the application.application.statusService.homeRef and application.application.statusService.contactEmail APIs.

Uncaught Exception Debugging

Uncaught exceptions in your code will automatically set the HTTP response status code to 500 ("internal server error"), but here you can configure the content of that response.
When "debug" is true, Prudence will return a very detailed HTML debug page. Because this might reveal your application's internal data, make sure to set "debug" to false for production deployments.
When "debug" is false, the default error template page will be displayed.

app.settings.code

Here you can control how Prudence deals with your code:

app.settings.templates

Configure templates resources here.

app.settings.caching

Configure caching here.

app.settings.compression

Global settings for resource compression. Note that compression can be turned on or off per individual resource type.

app.settings.uploads

Configure file upload behavior here.

app.settings.mediaTypes

This dict maps filename extensions to MIME types.
Prudence recognizes many common file types by default: for example, "png" is mapped to "image/png". However, using this setting you can define additional mappings or change the default ones. Note that each filename extension can be mapped to one and only one MIME type.
This setting is used mostly for textual and static resources. For example, a template resource named "person.t.html" will have the default "text/html" MIME type (which you can change in scriptlet code via the conversation.mediaType APIs), and a static resource named "logo.png" will have the "image/png" MIME type.
For manual resources, you define their supported MIME types manually in handleInit. There, you can refer to MIME types directly via the conversation.addMediaTypeByName API, or you can look them up from this setting using the conversation.addMediaTypeByExtension API.
An example:
app.settings = {
	...
	mediaTypes: {
		webm: 'video/webm',
		msh: 'model/mesh'
	}
}
Note for PHP: You may notice that the "default" template's settings.js sets the "text/html" MIME type for the "php" extension. The reason for this is that ".php" files you put in resources are usually expected to output HTML. You may change if you require a different behavior.

app.settings.distributed

Configure clusters here. These settings are for the usage of Hazelcast.
To learn how to configure general Hazelcast settings, see the configuration guide.

app.settings.routing

Though routing is configured in routing.js, here we can configure how routing is handled.

app.settings.logger

Set this string to override the default logger name. If not set, it will be the name of the application's subdirectory. The actual logger name will be this value prefixed with "prudence.".
See the logging chapter for a complete discussion.

app.globals

Use this for custom settings for your application: values here will become application.globals when your application is running. Note that Prudence also supports localized settings via inversion of control.
This dict is "flattened" using dot separators. For example, the following:
app.globals = {
	database: {
		driver: 'mysql',
		table: {
			db: 'myapp',
			name: 'users'
		}
	}
}
…would be interpreted as if it were:
app.globals = {
	'database.driver': 'mysql',
	'database.table.db': 'myapp',
	'database.table.name': 'users'
}
In your code, you would access these values using the application.globals API:
var driver = application.globals.get('database.driver')

Lazy Initialization

Lazy initialization allows you to defer the initialization of an application.global to its actual use within your application. This feature can be used to solve three different problems:
The mechanism relies on evaluation of JavaScript code, and thus is only available for other JavaScript code in your applications. However, similar principles can be used for other languages that support eval. Study the library's code to see how it's done!
Double Dot
Lazy globals can be defined using a double-dot key. Here's an example for settings.js:
app.globals = {
	services: {
		email: {
			'..': {
				dependencies: '/services/email/',
				name: 'EmailService',
				config: {
					smtp: 'localhost',
					origin: 'service@myapp.org'
				}
			}
		}
	}
}
The lazy configuration has three settings, which are used when the object is initialized:
For this example, here's how our "/libraries/services/email.js" file could look:
function EmailService(config) {
	this.smtp = config.smtp
	this.origin = config.origin
​
	this.send = function(destination, message) {
		application.logger.info('Sending an email from ' + config.origin + ' to ' + destination)
		...
	}
}
To actually access the object in the application, while possibly triggering lazy initialization (if it was not initialized already), we need the Prudence.Lazy library:
document.require('/prudence/lazy/')
​
var emailService = Prudence.Lazy.getGlobalEntry(
	'services.email',
	null,
	function(c) { return eval(c)() })
​
emailService.send('test@myapp.org', 'Hello, this is a test message!')
That third argument requires some explanation: it is a closure (anonymous function) that accepts the constructor source code and evaluates it locally. It will be called only if the global has not yet been initialized. Thus, in all calls except the first, this would be a very fast application.global lookup.
Triple Dot
The lazy initialization mechanism can optimize for dicts and arrays of lazy entries using the triple-dot key:
app.globals = {
	emailServices: {
		'...': {
			moderator: {
				dependencies: '/services/email/', name: 'EmailService',
				config: {smtp: 'localhost', origin: 'moderator@myapp.org'}
			},
			admin: {
				dependencies: '/services/email/', name: 'EmailService',
				config: {smtp: 'localhost', origin: 'admin@myapp.org'}
			}
		}
	},
	nodes: {
		'...': [{
			dependencies: '/services/nodes', name: 'Node',
			config: {address: 'node1.myapp.org'}
		}, {
			dependencies: '/services/nodes', name: 'Node',
			config: {address: 'node2.myapp.org'}
		}]
	}
}
To use them:
document.require('/prudence/lazy/')
​
var emailServices = Prudence.Lazy.getGlobalMap(
	'emailServices',
	null,
	function(c) { return eval(c)() })
​
emailServices.moderator.send('test@myapp.org', 'Hello, this is a test message!')
​
var nodes = Prudence.Lazy.getGlobalList(
	'nodes',
	null,
	function(c) { return eval(c)() })
Note that for this optimization the entire dict or array will be created at once and stored in a single application.global.
Forcing Re-Initialization
If you edit the source file for the lazy-initialized object—say, our "email.js"—you would not see your changes. The reason is that the object has already been initialized and stored in memory: the recompiled functions would not be hooked to the already-existing object (really just a dict in JavaScript).
You can, however, force re-initialization using the API:
Prudence.Lazy.getGlobalEntry('services.email').reset()
This works on dicts and arrays, too:
Prudence.Lazy.getGlobalMap('emailServices').reset()
To do this live, you can use the live execution mechanism.
Concurrency Note
The lazy mechanism is thread-safe: if you call getGlobalEntry concurrently, it will make sure to store only the first initialized object. You can thus be sure that you will always get the same instance of the lazy-initialized object in your application.
However, take note: it is possible that under high-concurrency situations the object would be created more than once, even if subsequent objects after the first are discarded. This might be a problem if your object creation is very heavy. Unfortunately, there is no easy way around this: allowing for multiple object creation is key to making the lazy mechanism perform well (it does no locking). If this is a problem for you, you will have to find your own way to avoid simultaneous initialization: possibly locking internally within your object constructor, and checking to make sure that the constructor is not being simultaneously called by another thread.

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