In Cernunnos, tasks and phrases use
Request Attributes as a common means of collaborating with one another. Request attributes are a collection of contextual information organized as a
Map: attribute values may be any type of
Object, but keys are always
Strings.
You can create request attributes in several ways; perhaps the most common (and most broadly applicable) method is the
<with-attribute> task
. This task registers any
Object (specified by the
VALUE reagent) under any name (specified by the
KEY). The resulting name/value pair will be visible to all subtasks: this is the role and the entire purpose of the
<with-attribute> task.
Conversely, some tasks that set request attributes have other duties. The
<sql-connection> task
is a great example.
<sql-connection> is responsible for opening a JDBC connection to an SQL data source. This connection is then made available to subtasks of
<sql-connection>, which use it to execute SQL statements. Once all subtasks have completed,
<sql-connection> closes the SQL connection in accordance with JDBC best practices.
You can optionally designate the name of the request attribute that will contain the SQL connection through the
ATTRIBUTE_NAME reagent. If you don't specify a name, a default name will be used (more on this topic below). Tasks that open or establish resources for the benefit of subtasks commonly support the
ATTRIBUTE_NAME reagent. These include
<invoke-method>
,
<node-iterator>
, and
<print-stream>
.
The usual way to access the value of a request attribute is through the
${req()} phrase
. Just put the name of the desired request attribute between the parentheses, like so:
${req(Attributes.LOCATION)}
Just like tasks, phrases use reagents to accept inputs; the expression '
Attributes.LOCATION' above is associated with the
KEY reagent of
${req()}. Unlike tasks, phrases typically have only one reagent.
Accessing request attributes is very common in Cernunnos. The
${req()} phrase, therefore, is the
default phrase implementation. Since it's the default, you don't have to specify it explicitly to use it.
The expression:
${req(Attributes.LOCATION)}
is usually abbreviated as:
${Attributes.LOCATION}
Request attributes exhibit some characteristics that may catch unwary programmers off guard; don't worry, they will make complete sense one you get used to them (we hope). Be aware of the following considerations when dealing with request attributes.
Request attributes have a lifecycle: they exist only within the scope of the task that creates them. In other words, they are visible to descendants, but not to ancestors or siblings.
Request attributes may also cover other request attributes. This process occurs whenever a task registers an attribute with the same name as an existing attribute. In this case, the new attribute will be visible within the scope of the task that (re)registered it; the covered attribute will be inaccessible.
Many tasks and phrases provide default values for some or all of their reagents through standard, well-known request attributes. Consider this example:
Who told
<sql-connection> how to connect to the database? Who told
<sql-statement> or
${sql()} what JDBC connection to use?
No one did -- at least, not explicitly. This information is communicated through a system of conventional request attributes. Since
<sql-connection>,
<sql-statement>, and
${sql()} each agree on the convention, you can leave these mundane details out of your scripts in 99% of cases. For the remaining 1% of cases, you have the opportunity to specify a
DataSource or
Connection object explicitly.
Request attributes that serve as default values for reagents are given names according to a convention: first a name like "Attributes" or "SqlAttributes" (roughly corresponding to Java package names), followed by a period '.' character, followed by a noun in uppercase (multi-word nouns are separated by underscore '_' characters). For example:
Attributes.CONTEXT
Attributes.LOCATION
SqlAttributes.DATA_SOURCE
XmlAttributes.ENTITY_RESOLVER
Ad-hoc request attributes need not --
and should not -- adhere to this convention. User-defined request attributes that contain special characters may not play well with some
Task or
Phrase implementations. Use camel-case names for request attributes, like Java variables (
e.g. 'myFile' or 'theXslTemplateLocation').
Depending on how you use Cernunnos, one or more request attributes may be present in the collection even before the first task is invoked.
Attributes.ORIGIN
The value of this attribute is a URL (in
String form) representing the absolute location of the current script. It set by the Cernunnos runtime whenever a
Task object is created from a file -- whether read over HTTP, over FTP, from the classpath, from the local file system,
etc.
Attributes.ORIGIN is tremendously useful because it allows you to reference additional resources -- XSLT stylesheets, properties files, other Cernunnos scripts,
etc. -- using relative path expressions. Thanks to this attribute, you don't have to know where resources will be when they are ultimately deployed; you only have to know where they are
relative to the script that uses them. Pass
Attributes.ORIGIN to the
CONTEXT reagent of a task that uses an external resource; it will then evaluate the
LOCATION of that resource relative to the current script.
There are, moreover, several tasks that use
Attributes.ORIGIN as the default value for their
CONTEXT reagent. Some examples are
<properties>
and
<crn>
. These tasks will evaluate
LOCATION relative to the current script if
CONTEXT is omitted.
When you invoke Cernunnos from the command line, you may optionally specify request attributes as command line parameters. Take a look at this example:
> crn good-advice.crn many hands make light work INFO [main] runtime.ScriptRunner.[] Nov/28 21:00:14 - ************************************************** ** Invoking ScriptRunner.run(Task, TaskRequest) ** TaskRequest contains 6 elements ** - $3=make ** - $1=many ** - Attributes.ORIGIN=file:/C:/HOME/danann/cernunnos/good-advice.crn ** - $5=work ** - $2=hands ** - $4=light **************************************************
The first parameter is always the location of the script (can be relative, absolute, or a URL). Subsequent parameters are added to the collection of request attributes as
$1,
$2, and so on.
Cernunnos has been used to develop several Java Portlets. For detailed instructions on how to create portlets with Cernunnos, refer to this article.
The Cernunnos portlet class (
org.danann.cernunnos.runtime.web.CernunnosPortlet) can pre-load the request with useful attributes whenever it invokes a script. For starters, it creates request attributes for the two objects provided by the portlet container in each action or render cycle:
WebAttributes.REQUEST
WebAttributes.RESPONSE
These will be set to instances of
ActionRequest/
ActionResponse or
RenderRequest/
RenderResponse in the
processAction and
render methods, respectively.
In addition, you may optionally define a collection of request attributes for your portlet using
spring dependency injection. These attributes my be of any type, and will be accessible to all scripts in both
processAction and
render.