Skip to content

Concepts

Alex Vigdor edited this page Mar 26, 2018 · 1 revision

Groovity Concepts

In this section we introduce several core concepts to help you get familiar with Groovity.

  • Groovity source files (where can my groovity scripts live?)
  • Compilation (how and when are my scripts compiled?)
  • Binding (how do I inject custom beans for use in groovity scripts?)
  • Linking (how do my groovity scripts invoke each other?)
  • Libraries (how do I write and document groovy library code for re-use?)
  • Tags (how do I implement and document custom tags/global functions?)
  • Application Lifecycle (how do I initialize and later clean up resources?)

Source files

Groovity scripts can be compiled during the application build process, at application startup, automatically or manually at runtime. A deployed Groovity application can run on pre-compiled groovity jars generated during the build process, from dynamically compiled scripts loaded from a local disk, HTTP or S3, or a combination of both.

Compilation

Groovity scripts compile down to normal java classes that can be stored in JAR files for deployment, or to speed up restarts for applications that leverage runtime compilation. The configuration allows you to specify automatic lifecycle phases for both compilation and jar file use.

STARTUP phase:

When the application is being initialized, if the JAR STARTUP phase is enabled, groovy classes will first be loaded from jar files if they are present. If the SOURCE STARTUP phase is enabled, the application will then make sure to compile any groovy scripts that have been added or updated since the jars were built before the application completes startup, guaranteeing the latest code is in place before it starts handling requests. If SOURCE STARTUP is enabled alone without JAR startup, all files will be compiled from source on every restart. If JAR STARTUP is enabled without SOURCE STARTUP, the application will begin running with the exact same compiled code base it was deployed or shut down with, but depending on the RUNTIME phase configuration it may automatically recompile updated scripts in the background after startup.

RUNTIME phase:

If SOURCE RUNTIME phase is enabled, the application will automatically poll the groovy script source according to the specified interval, and recompile updates in the background. This guarantees the application automatically stays up-to-date with code changes without restarting or redeploying. If JAR RUNTIME is enabled, the application will write out jar files that can be used to speed the STARTUP phase. If SOURCE RUNTIME is not enabled, changes to groovy scripts can still be compiled manually at runtime using the admin UI/API, which will still generate JAR files if JAR RUNTIME is enabled.

Binding

A core concept in Groovy is the Binding, which defines a variable scope for script execution. Any variable stored in the binding can be accessed by name in any groovy script that shares that binding. Groovity allows you to wire in a BindingDecorator that gives you complete control at the application level over what beans or services are available by default in all groovy scripts. BindingDecorators are even applied for static initializers, so your startup logic can leverage things like datasources and services added by the BindingDecorator.

Groovity introduces a syntax for declaring arguments, which is used to validate, coerce and populate values in the binding that might come from the command line or servlet request parameters.

Groovity Servlet binding

Groovity servlet adds several additional objects to the binding for all scripts, including

request – the HttpServletRequest being processed, response – the HttpServletResponse being processed, log – an apache commons logging Log for the script class

If your script uses the content negotiation features of Groovity servlet, it receives an additional object in the binding called 'variant' - a map that may include "output" for the negotiated content type, "language" or "charset", and any custom headers you have specified to use in varying the response, e.g. X_DEVICE_CLASS.

In addition, any query string parameters in the servlet request are automatically mapped to declared arguments, as are any path parameters matched in a JAX-RS path declaration. And within a groovity script, as in groovy, any variable declared without a “def” or type keyword is automatically placed in the binding.

Classes and Linking

In computer science, linking refers to the mechanism by which code in one module can execute code in another module. In Java and plain Groovy for example it is possible to write classes that can then be referred to by name in other parts of the code. However any changes to a class are likely to cause verification errors in calling code unless references to that class are recompiled; this makes Java and groovy a poor fit for dynamic scripting, as complex dependency checking and extensive recompilation is needed avoid these verification errors.

Groovity takes a different approach; every individual groovity script is compiled in an isolated classloader, with no class-level visibility into other scripts. Linking to other scripts or tags is done dynamically at runtime and method invocation and property access relies on Groovy’s dynamic access to objects. Think of it as loose coupling by design. The only way to tightly couple classes in Groovity is to define them inside the same script (aka the same compilation unit).

To load another groovy script for use as a library, service or bean, groovity offers a load() function, e.g.

load('/services/someService');

load() returns a Script object which may be assigned to any variable in any scope, or chained with a method call.

def myLib = load('/lib/someLib')
load('/lib/action').doSomething()

However if you declare a load call at the top level of your script without assigning it to a variable or chaining a method call or property access to it, Groovity automatically creates a field to store the returned instance of the library; the name of this field is taken from the last component of the script path following the final slash. Therefore the following statements are equivalent and would cause a compiler error if located together in the same script:

load('/lib/mailService')

//the above is shorthand and exactly equivalent to

@Field mailService = load('/lib/mailService')

Groovity also offers a run() method that loads AND executes the body of another script in a single step, and returns the result of the script execution; if this result is a Writable, like a streaming template <~ Hello World ~> or GString """Hello World""", it is automatically written to out.

run('/templates/widgetA')

Libraries

A library is a script that defines some re-usable functions that can be called from other scripts. Libraries are loaded using the load() command. Libraries are loaded no more than once per request, and can access the request binding. Library functions can be annotated inline with documentation to appear in the dashboard. Example:

@Field username = request.cookies?.find{it.name=="userName"}?.value;

@Function(info=”Greet the current user”);
def getPersonalGreeting(@Arg(info=”Text to greet user with”) greeting){
            return “${greeting} ${username?:’’}”;
}

Tags

Tags are a special sort of function that takes a map of named parameters, as well as a body function. Tags can be expressed inside streaming templates using an XML-style tag syntax, or can be called in regular script as global functions. Dozens of tags are built in and support a wide variety of operations; live documentation of all tags is available via the admin dashboard. The framework also allows you to develop your own custom tags. The class name of a custom tag automatically gets turned into a lowercase tag/function name. Tags must be annotated inline to generate live documentation in the dashboard.

Tags are distinct from Libraries in that they are stateless; a Tag is a singleton and can only access the runtime binding by way of the body closure that gets passed in. This means tag code can be a bit more verbose or less friendly to read than library code, however tags are extremely clean and easy to consume, and execute very efficiently. The Taggable interface defines some convenience methods to simplify common tasks in tag code:

resolve(attributes, name, type)

Lookup a declared attribute of the tag and convert to a given type. def limitAttr = resolve(attributes,'limit',Integer.class)

get(body, name)

Lookup a variable from the binding associated with the tag body def boundVar = get(body,'someObj')

bind(body, name, value)

Add a variable to the binding associated with the tag body bind(body,'result',[hi:'there'])

unbind(body, name)

Remove a variable from the binding associated with the tag body unbind(body,'workingList')

Tags may also implement init() or destroy() methods to be invoked as the hosting Groovity is started and shutdown, respectively.

Tags can be written in Groovity or Java. Groovity tags are automatically discovered by the framework; tags written in Java must implement the Taggable interface and are discovered using the java ServiceLoader framework. In order to wire in Java tags your module must declare a manifest file with one class name per line at /META-INF/services/com.disney.groovity.Taggable

Example

---- /lib/myCustomTag.grvt ----

static wrappers = [‘strong’,’em’];
//document the tag and it's attributes
@Tag(
info =Use if you really mean it”,
body=”the message you want to pronounce”,
attrs= [
@Attr( name=”exclaim”,
info=Whether to add exclamation points”,
required=false)
]
)
public class Really{
            public tag(Map attrs, Closure body){
                        def out = get(body,'out')
                        wrappers.each{ out <<<${it}>” };
                        out << body.call();
                        if(resolve(attrs,'exclaim',Boolean.class)){
                                    out <<!!!”;
                       }
                        wrappers.reverse().each{ out <<</${it}>” };
            }
}

---- /myPage.grvt ----

really(exclaim:true,{ "I mean it" })
<~<g:really exclaim=”true”>I really do</g:really>~>

---- /myPage output ----

<strong><em>I mean it!!!</em></strong><strong><em>I really do!!!</em></strong>

Application Lifecycle

It is possible to hook into application startup and shutdown within a groovy script; Groovity automatically detects specially named "init" or "destroy" static methods on scripts or inner classes that can be used to perform setup or teardown activities. The init() function is called on application startup and/or when a groovy script is (re)compiled. Similarly the destroy() method is called when a groovy script is replaced with a newly compiled version, and will also be called during a clean application shutdown. These static methods have access to the default binding and can call other groovy scripts; the framework will alert you if it detects circular references.

This example script uses a background loader thread to call a factory script every five seconds as soon as the application starts up; when executed at runtime it performs a JSON serialization of the data that is held in memory by the loader.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

out = response.writer;

//access the static cached data on request and write to response
write(value:DataLoader.data, pretty:true);

class DataLoader{
    static def scheduler;
    static def data;
    static def log;

    static init(){//initialize scheduled task at startup time
        scheduler = Executors.newScheduledThreadPool(1);
        log(info:"Created scheduler ${scheduler}");

        //schedule immediate and recurring load of data
        scheduler.scheduleAtFixedRate(new Runnable(){
            public void run(){
                log(info:"Running background task");
                data = load('/factory').loadRSSItems("http://abcnews.go.com/meta/search/results?searchtext=coffee&format=rss");
            }
        }, 0, 5, TimeUnit.SECONDS)
    }

    static destroy(){
        //shutdown scheduler at recompile or shutdown time
        log(info:"Destroying scheduler ${scheduler}");
        scheduler.shutdown();
    }
}

And here is the sample factory script on the receiving end of that groovy call:

public List<RssItem> loadRSSItems(String url){
    return new XmlSlurper().parse(url).channel.item.collect{
        new RssItem([title:it.title, link:it.link])
    };
}
public class RssItem{
    public String title;
    public String link;
}
Clone this wiki locally