Writing Groovy handlers
Configuration
Groovy scripts and templates are generally used without explicit configuration in zero.config. For example, these files are generally served from the public folder, processed as resource handlers, and used as renderers. Such usage requires that the file be placed in the appropriate directories. For more information, see the following topics:
- file serving
- See the File serving article.
- resource handlers
- See the Resource (REST) handling article.
- renderers
- See the Response rendering article.
For those advanced cases where you want to register a Groovy script or template as an event handler in zero.config (for example, if you want to use a Groovy script to implement a requestBegin event handler), see the Event processing and event handler registration section of the Configuration articles for details.
Groovy scripts and classes
Groovy-based event handlers might be scripts or classes. Scripts can be a unstructured, as shown in the following example:
println "Hello World"
Scripts can also be written as a set of event-specific methods, as shown in the following example:
def onGET() {
println "Hello World"
}
Most events support a "method-then-script" invocation strategy. This means that the unstructured script is invoked only if the corresponding method is not found. The exception is resource events (for example list and retrieve) that support only method-based invocation. Groovy-based resource handlers must be either scripts with methods or classes.
GET and POST) are treated as a group in a method-then-script invocation. Therefore, if any of the group methods are defined (for example onGET), then the request can only be served by a method and the unstructured script is not invoked.
Groovy classes include the more formal decorations of an object-oriented programming model, as shown in the following example:
import zero.core.groovysupport.ZeroObject
public class MyHandler implements ZeroObject {
public void onGET() {
println "Hello World"
}
}
ZeroObject is a marker interface. Implementations of ZeroObject and Groovy scripts obtain special WebSphere sMash support in the form of bindings and print and println bound to the WebSphere sMash writer. For more information on bindings, see the Bindings section.
Bindings
Groovy supports bindings as a mechanism to set variables and methods on a script. WebSphere sMash provides default bindings and a mechanism for you to add additional bindings.
Bindings do not work within static methods and blocks because a check (to see if they are defined) is done at compile-time when they are not yet available.
Default bindings
The following table shows default bindings.
| Name | Description |
|---|---|
app |
Global context accessor bound to /app. |
config |
Global context accessor bound to /config . |
event |
Global context accessor bound to /event . |
headers |
Global context accessor bound to /request/headers . |
request |
Global context accessor bound to /request . |
user |
Global context accessor bound to /user . |
zget() |
Set of convenience methods from GlobalContext |
zput() |
|
zputs() |
|
zpost() |
|
zdelete() |
|
zlist() |
|
zlistAll() |
|
zcontains() |
|
zdump() |
|
logger |
Instance of zero.core.logging.GroovyLogger for logging from a Groovy script |
render() |
Invokes the zero.core.views.ViewEngine.render() renderer |
getRelativeUri() |
Set of convenience methods from Class URIUtils |
getAbsoluteUri() |
|
getRequestedUri() |
|
getVFile() |
Returns a File reference based on the virtualized directories. The method is bound to zero.core.utils.VDirectory.getVFile(String, List) |
listFiles() |
Returns a list of File found within a virtualized directory. The method is bound to zero.core.utils.VDirectory.listFiles(String) |
Additional bindings
Defining additional bindings is a two-step process, as described in the following steps:
-
Provide a Java implementation of
zero.core.groovysupport.bindings.BindingHandlerto define the bindings. Examples can be found in the WebSphere sMash source code within thezero.core.groovysupport.bindingspackage. -
Register your
BindingHandlerwith a script type (based on the file suffix, such as.groovyor.gtas shown in the following example:/config/bindings/.groovy += ["zero.core.groovysupport.bindings.DefaultBindings"]
Global context accessors
Global context accessors are objects that support Groovy interactions with the global context. These objects support GPath syntax for read and write, as shown in the following table:
| Global context API | Groovy equivalent |
|---|---|
zget("/request/method") |
def m == request.method[] |
zget("/request/params/name#1") |
def name == request.params.name[1] |
zput("/request/status", 204) |
request.status == 204 |
A global context URI is generally structured as /<zone>/<path>[#<value_path>]. Global context accessors support GPath notation for the zone/path portion; the value path is expressed within brackets (without the '#').
Details about using value paths within the global context are found in the Global context article.
Groovy scripts invoking Groovy scripts
Groovy scripts in the public and app/resources virtualized directories can access other Groovy scripts and classes from the app/scripts virtualized directory.
Invoking a Groovy class
To use a Groovy class defined in the scripts folder, import the appropriate class name (including package names). You can either instantiate the class or invoke static methods in the class. In the following example, a Helper class is defined in a app/scripts/help/support/Helper.groovy file.
package help.support
class Helper {
def help() {
return "Helping"
}
}
This class can be used from another script with the following snippet of code:
import help.support.Helper h = new Helper() h.help()
Invoking a Groovy script
The steps to define and import a Groovy script from the app/scripts virtualized directory are similar. Groovy scripts can be invoked as an unstructured script (invokeScript) or by method (invokeMethod). The following example defines a script in the app/scripts/helper.groovy file:
print "Helper Script"
def helperMethod() {
print "Helper Method"
}
def helperMethodWithArgs(foo, bar) {
print "Helper Method with $foo $bar"
}
This script can be invoked by the running the following example:
// To run the unstructured script: results are "Helper Script" invokeScript ( "helper.groovy" ) // To invoke a specific method: results are "Helper Method" invokeMethod ( "helper.groovy", "helperMethod", null ) // To invoke a specific method with args: results are "Helper Method x y" invokeMethod ( "helper.groovy", "helperMethod", "x", "y" )
Using Groovy templates
WebSphere sMash treats files with a .gt suffix as Groovy templates. Groovy templates can be used when a file
contains mostly static text content. WebSphere sMash uses Groovy's SimpleTemplateEngine to provide template support. The following example shows a Groovy template :
<html>
<body>
<h1>
Today is <%= new Date() %>
</h1>
<% for (name in ['john', 'jack', 'joe']) { %>
<ul>
<li>${name}</li>
</ul>
<% } %>
</body>
</html>
Groovy statements are enclosed with open and closed angle brackets: <% and %>. Groovy expressions that evaluate to strings can be placed within <%= and %>.
Rendering templates to the output stream
The view renderer renders a template directly to the output stream. See the view renderer information in the Response rendering article for more information. This is useful when you are invoking the template in situ for the response. The view renderer is invoked from a Groovy template as shown in the following example:
<% request.view="otherTemplate.gt" render() %>
Rendering templates to a String
Templates can be used to render strings for other processing, such as content items passed into the Atom renderer. This is accomplished with the invokeTemplate method, as shown in the following example:
def content = invokeTemplate("foo/bar/mytemplate.gt")
// process content
Similar to the view renderer, the path (for example foo/bar/template.gt) must be relative to the app/views of your application virtualized directory.
Logging from a Groovy Script
Groovy scripts and templates have a binding for logger, which is an instance of zero.core.logging.GroovyLogger. The logger binding supports two forms of invocation. In each invocation form, the log message is specified as a closure. The closure is not evaluated unless the specified log level is met.
Invoking with a closure
Invoking with a closure is a simpler form of the logging API. The sourceClass and sourceMethod are derived from the closure as shown in the following example.
File:
<appRoot>/public/a/b/test.groovy
package a.b
def onGET() {
logger.FINE { "Hello world." }
}
The following sample is an excerpt from: <appRoot>/config/logging.properties and shows
logging specified for exactly this Groovy artifact.
a.b.test.level=ALL
The following sample shows log results.
2007-08-09 21:42:08 a.b.test::onGET Thread-10 FINE [ Hello world. ]
Invoking with named parameters
Invoking with named parameters allows the caller to optionally specify the sourceClass and sourceMethod. If not specified, sourceClass and sourceMethod are derived from the closure.
File:
<appRoot>/public/a/b/test.groovy
The following example shows a possible usage that enables arbitrary grouping of Groovy artifacts, which might be handy for specifying consolidated log levels.
package a.b
def onGET() {
logger.FINE ( clazz: "groovy.a.b.test", method: "boo", msg: { "Hello world." })
}
The following sample is an excerpt from: <appRoot>/config/logging.properties.
groovy.level=ALL
The following sample shows log results.
9:42:23 PM groovy.a.b.test::boo Thread-11 FINE [ Hello world. ]
Troubleshooting
Groovy Class Reloading Behavior
In most instances, the WebSphere sMash runtime will force a reload of any .groovy files that are modified on the
file system. This behavior allows a very dynamic development environment, where the Groovy file can be edited
with the application running, and the script will be recompiled on the next request.
There are exceptions to when this is possible, however. The WebSphere sMash runtime will not traverse across all of the used Groovy scripts on an invocation, so if a Groovy script calls another Groovy script, then the called script's compiled classes will not be updated if the script was updated.
The net result of this behavior is that if a Groovy script is always only called from other Groovy scripts, that called Groovy script will only be compiled once. For changes to that script to take effect, the application will have to be recycled.
InvokerInvocationException: No such property
If your Groovy script is failing with the ...InvokerInvocationException: No such property: ... message it is because missing classes might be reported as "No such property", due to Groovy's attempt to interpret the package as a GPath expression. For example, consider the following misspelled package:
zero.kore.utils.URIUtils.getRelativeUri("")
This example produces the following results:
"...InvokerInvocationException: No such property: zero"