Overview
Location of the configuration file
The default location for the configuration file is KAURI-INF/representations.groovy. In the Maven source tree layout, this is at src/main/kauri/representations.groovy.
If you do not have such a configuration file, it suffices to create it and it will be automatically used. If you are running in source mode, this change will be picked up immediately, if you're not running in source mode you need to rebuild the module and restart Kauri.
Syntax
The configuration file is a Groovy program, using the GroovyBuilder pattern. This is the same as for the routing. The routing documentation contains details on motivation and basic syntax information. However, everything you need to know for normal usage can be found below.
Examples
Below you'll find the basic reference information. To see examples of how this all fits together, check out the samples included with Kauri.
Basic structure
The configuration consists of three sections:
- A section for mapping logical representation names to actual representations.
- A section for mapping exception classes to representations.
- A section for mapping error status codes to representations.
This is the minimal structure containing the placeholders for those three sections:
builder.representations {
select {
}
exceptions {
}
errors {
}
}
Mapping logical representation names to representations
The basic tool for this mapping is a switch-structure using 'select' and 'when' as the syntactic elements:
select {
when(name: "foo") {
template(src: "module:/templates/foo.xml")
}
when(name: "bar", mediaType: "text/html") {
template(src: "module:/templates/bar.xml")
}
when(name: "something/{remainder,all}") {
select {
when(name: "something/A/B/C") {
template(src: "module:/templates/A.xml")
}
when(name: "something/X/Y/Z") {
template(src: "module:/templates/X.xml")
}
}
}
}
Here is everything you need to know about the 'when' node:
- A name parameter specifies a pattern to match on the logical representation name.
- A mediaType parameter specifies a pattern to match on the requested media type.
- The name and mediaType parameters can be combined or used by themselves. A when clause without name or mediaType parameter will always match.
- The patterns are in URI template syntax, hence similar to what is used in the routing configuration. In contrast to the routing, the patterns are always matched in "equals" mode, meaning that the pattern must match the whole input string.
- during the matching process, the when nodes are processed in order (not with a best match, as done in the routing).
A 'when' node should contain exactly one child node, which can be:
- a node that defines the representation to be created. In the above example, we used the 'template' node, but there are others available as well.
- again a select node. Nesting select's inside when's is mainly useful if you have a long list of logical names: bringing some hierarchy into the matching process might give better performance.
It is not invalid for a when node to contain no child, but it won't do anything meaningful either, the matching process will continue with the next when node.
Mapping exceptions to representations
For mapping exceptions to an appropriate representation, a selection process based on the exception class name is available. Here is some example syntax:
exceptions {
exception(classes: ["my.company.WrapperException"], unroll: true)
exception(classes: ["my.company.FooException", "my.company.BarException"]) {
template(src: "module:/templates/error_not_found.xml", statusCode: 404)
}
exception {
template(src: "module:/templates/error.xml", statusCode: 500);
}
}
The matching works as follows:
- The system runs over the exception nodes. If its classes parameter lists a class which is the same as the exception class, or a superclass thereof (= instanceof check), then the exception node matches. An exception node with no (or an empty) classes parameter will always match, except when searching for causes (see step 2).
- If the exception node matches and has an "unroll: true" parameter, the system will get the cause of the exception (= the first nested exception), if it exists, and go back to step (1). If there is no cause, or the handling for the cause didn't return anything, processing will continue here.
- If the exception node matches and has a child element specifying the representation to create, then that will be the representation used.
- If the exception node matches and has no child element, then the system moves on to the next exception node.
Mapping error status codes to representations
This mapping is very straightforward: it maps a status code or a list of status codes to a representation.
errors(overwrite: false) {
error(status: 404) {
template(src: "module:/templates/error_not_found.xml")
}
error(status: 500..599) {
template(src: "module:/templates/server_error.xml")
}
error {
template(src: "module:/templates/error.xml")
}
}
The matching rules are as follows:
- the rules are evaluated in order, the first one which matches is used
- the status can be a single integer or a list of integers. Note that:
- a "Groovy range" is also a list, e.g. 500..599
- Groovy supports all kinds of groovy ways of creating lists
- an error-rule without a status parameter will always match. Hence it only makes sense to have one such rule, and to have it at the end.
The errors node can have an optional attribute 'overwrite', indicating that error responses should always be overwritten with the representations configured here. By default this is false, thus if there is already a response representation present, it will be left untouched.
The representations used inside error { } should not carry a statusCode parameter, so it is disallowed to override the status code.
Multiple formats for exceptions and errors
Sometimes you might want exception and error responses to be formatted differently depending on who makes the request: if it is an Ajax call, an error response formatted as JSON will likely be preferred over a human-oriented HTML error page.
When a module contains only resources intended for use in Ajax calls or only resources intended for human display, you can always format exceptions and errors in the same way.
But when the resources might be accessed in different ways, it is more useful to let the client specify how it wants errors to be formatted. This format is not necessarily related to the format requested: an Ajax call might retrieve a blurb of HTML but still want an error formatted as JSON.
This client-specified error and exception formatting is possible by having multiple exceptions and errors sections in the configuration, carrying a media attribute.
/* This exception section will be used when the client does not specify a preferred format */
exceptions {
}
exceptions(media: "json") {
}
errors {
}
errors(media: "json") {
}
The client can specify the requested format (media) in two ways:
- a request parameter called kauri-error-media
- a request header called X-Kauri-Error-Media
The representation builder will then use the matching exceptions/errors section.
Representations
To create an actual representation, a few built-in solutions are available, and a way to refer to your own classes to create the representation.
template
This creates a template representation, the most common way of producing an HTML or XML response. This uses Kauri's template engine.
Syntax:
template(src: "uri")
The src parameter specifies the template file location, typically this will be a URI using the module protocol such as module:/templates/foo.xml
resource
This creates a representation which returns a static resource.
Syntax:
resource(src: "uri")
custom
This allows to use your own code to create the representation.
Syntax:
custom(ofClass: "fully qualified class name") custom(ofBean: "spring bean name")
You can either specify a class or refer to a bean in the Spring container by name. Either way, the resulting object should implement the interface org.kauriproject.representation.RepresentationFactory. See its javadoc for more information.
jsonStatus
Formats an exception or error as json. This should only be used in the exceptions or errors sections.
Syntax:
jsonStatus(statusCode: 500)
The output produced is of the following kind:
{
status: "(HTTP status code)",
description: "(description of status code)",
throwable: {
message: "(exception message)",
type: "(exception fully qualified class name)",
stackTrace: [
{class: "...", method: "...", native: true|false, file: "...", line: number}
],
cause: { (nested throwable description) }
}
}
The description and throwable will only be present when available.
jsonStatus2
An alternative for jsonStatus, with an output where the stacktrace is put into one string property, but with the messages and types of the various causes still available through json structure:
{
status: "(HTTP status code)",
description: "(description of status code)",
causes: [
{ message: "...", type: "(java exception class name"},
... more causes ...
],
stackTrace: "stack trace as provided by e.printStackTrace, containing \n and \t chars"
}
Reusing matched values
In the src attributes of the template and resource representations, you can refer to match values from the when nodes, using URI template syntax.
Example:
select {
when(name: "{name,all}") {
template(src: "module:/templates/{name}.xml")
}
}
If you have nested select's, you can use "../" syntax to walk up in the select node stack.
Common syntax
On each representation-creating node, you can use the following optional parameters.
|
Name |
Description |
|---|---|
|
statusCode |
an integer, specifying the HTTP status code for the response. For normal representations this is by default 200, for exceptions this is by default 500. |
|
mediaType |
the media type for the response. (todo: behavior and defaults depend on representation implementation) |