Groovy Scripts as REST Services
1 Overview
This article describes how to use Groovy scripts as REST services. We are going to consider these operations:
- Load script and save it in JCR.
- Instantiate script.
- Deploy newly created Class as RESTful service.
- Script Lifecycle Management.
- And finally we will discover simple example which can get JCR node UUID.
In this article we consider RESTful service compatible with JSR-311 specification. Currently last feature available in version 1.11-SNAPSHOT of JCR, 2.0-SNAPSHOT of WS and version 2.1.4-SNAPSHOT of core.
2 Loading script and save it in JCR
There are two ways to save a script in JCR. The first way is to save it at server startup time by using configuration.xml and the second way is to upload the script via HTTP.
2.1 Load script at startup time
This way can be used for load prepared scripts, for use this way we must configure
org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoaderPlugin. This is simple configuration example.
<external-component-plugins>
<target-component>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</target-component>
<component-plugin>
<name>test</name>
<set-method>addPlugin</set-method>
<type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoaderPlugin</type>
<init-params>
<value-param>
<name>repository</name>
<value>repository</value>
</value-param>
<value-param>
<name>workspace</name>
<value>production</value>
</value-param>
<value-param>
<name>node</name>
<value>/script/groovy</value>
</value-param>
<properties-param>
<name>JcrGroovyTest.groovy</name>
<property name="autoload" value="true" />
<property name="path" value="file:/home/andrew/JcrGroovyTest.groovy" />
</properties-param>
</init-params>
</component-plugin>
</external-component-plugins>
First
value-param sets JCR repository, second
value-param sets workspace and third one sets JCR node where scripts from plugin will be stored. If specified node does not exists then it will be created. List of scripts is set by
properties-params. Name of each
properties-param will be used as node name for stored script, property
autoload says to deploy this script at startup time, property
path sets the source of script to be loaded. In this example we try to load single script from local file
/home/andrew/JcrGroovyTest.groovy.
2.2 Load script via HTTP
This is samples of HTTP requests. In this examples we will upload script from file with name
test.groovy.
andrew@ossl:~> curl -u root:exo \
-X POST \
-H 'Content-type:script/groovy' \
--data-binary @test.groovy \
http://localhost:8080/rest/script/groovy/add/repository/production/script/groovy/test.groovy
This example imitate sending data with HTML form ('multipart/form-data'). Parameter
autoload is optional. If parameter
autoload=true then script will be instantiate and deploy script immediately.
andrew@ossl:~> curl -u root:exo \
-X POST \
-F "file=@test.groovy;name=test" \
-F "autoload=true" \
http://localhost:8080/rest/script/groovy/add/repository/production/script/groovy/test1.groovy
3 Instantiation
org.exoplatform.services.script.groovy.GroovyScriptInstantiator is part of project
exo.core.component.script.groovy.
GroovyScriptInstantiator can load script from specified URL and parse stream that contains Groovy source code. It has possibility inject component from Container in Groovy Class constructor. Configuration example:
<component>
<type>org.exoplatform.services.script.groovy.GroovyScriptInstantiator</type>
</component>
4 Deploy newly created Class as RESTful service
For deploy script automatically at server statup time its property
exo:autoload must be set as true.
org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader check JCR workspaces which were specified in configuration and deploy all auto-loadable scripts.
Example of configuration.
<component>
<type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</type>
<init-params>
<object-param>
<name>observation.config</name>
<object type="org.exoplatform.services.jcr.ext.script.groovy.ObservationListenerConfiguration">
<field name="repository">
<string>repository</string>
</field>
<field name="workspaces">
<collection type="java.util.ArrayList">
<value>
<string>production</string>
</value>
</collection>
</field>
</object>
</object-param>
</init-params>
</component>
In example above JCR workspace "production" will be checked for autoload scripts. At once this workspace will be listened for changes script's source code (property
jcr:data).
5 Script Lifecycle Management
If
GroovyScript2RestLoader configured as was decribed in previous section then all "autoload" scripts deployed. In first section we added script from file
/home/andrew/JcrGroovyTest.groovy to JCR node
/script/groovy/JcrGroovyTest.groovy, repository
repository, workspace
production. In section "Load script via HTTP" it was refered about load scripts via HTTP, there is an opportunity to manage the life cycle of script.
Undeploy script, which is alredy deployed:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy?state=false
Then deploy it again:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy?state=true
or even more simple:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy
Disable scripts autoloading, NOTE it does not change current state:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/repository/production/script/groovy/JcrGroovyTest.groovy/autoload?state=false
Enable it again:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/autoload/repository/production/script/groovy/JcrGroovyTest.groovy?state=true
and again more simpe variant:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/autoload/repository/production/script/groovy/JcrGroovyTest.groovy
Change script source code:
andrew@ossl:~> curl -u root:exo \
-X POST \
-H 'Content-type:script/groovy' \
--data-binary @JcrGroovyTest.groovy \
http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy
This example imitate sending data with HTML form ('multipart/form-data').
andrew@ossl:~> curl -u root:exo \
-X POST \
-F "file=@JcrGroovyTest.groovy;name=test" \
http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy
Remove script from JCR:
andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/delete/repository/production/script/groovy/JcrGroovyTest.groovy
6 Get node UUID example
Now we are going to try simple example of Groovy RESTfull service. There is one limitation, even if we use groovy we should use Java style code and decline to use dynamic types, but of course we can use it in private methods and feilds. Create file JcrGroovyTest.groovy, in this example I save it in my home directory
/home/andrew/JcrGroovyTest.groovy. Then configure
GroovyScript2RestLoaderPlugin as described in section
Load script at startup time.
import javax.jcr.Node
import javax.jcr.Session
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import org.exoplatform.services.jcr.RepositoryService
import org.exoplatform.services.jcr.ext.app.ThreadLocalSessionProviderService
@Path("groovy/test/{repository}/{workspace}")
public class JcrGroovyTest {
private RepositoryService repositoryService
private ThreadLocalSessionProviderService sessionProviderService
public JcrGroovyTest(RepositoryService repositoryService,
ThreadLocalSessionProviderService sessionProviderService) {
this.repositoryService = repositoryService
this.sessionProviderService = sessionProviderService
}
@GET
@Path("{path:.*}")
public String nodeUUID(@PathParam("repository") String repository,
@PathParam("workspace") String workspace,
@PathParam("path") String path) {
Session ses = null
try {
ses = sessionProviderService.getSessionProvider(null).getSession(workspace, repositoryService.getRepository(repository))
Node node = (Node) ses.getItem("/" + path)
return node.getUUID() + "\n"
} finally {
if (ses != null)
ses.logout()
}
}
}
After configuration is done start server. If configuration is correct and script does not have syntax error you should see next:

In screenshot we can see service deployed.
First create folder via WebDAV in repository production, folder name 'test'.
Now we can try access this service. Open another console and type command:
andrew@ossl:~> curl -u root:exo \
http://localhost:8080/rest/groovy/test/repository/production/test
Whe you try to execute this command you should have exception, because JCR node '/test' is not
referenceable and has not
UUID. We can try add mixin
mix:referenceable. To do this add one more method in script. Open script from local source code /home/andrew/JcrGroovyTest.groovy, add following code and save file.
@POST
@Path("{path:.*}")
public void addReferenceableMixin(@PathParam("repository") String repository,
@PathParam("workspace") String workspace,
@PathParam("path") String path) {
Session ses = null
try {
ses = sessionProviderService.getSessionProvider(null).getSession(workspace, repositoryService.getRepository(repository))
Node node = (Node) ses.getItem("/" + path)
node.addMixin("mix:referenceable")
ses.save()
} finally {
if (ses != null)
ses.logout()
}
}
Now we can try to change script deployed on the server without server restart. Type in console next command:
andrew@ossl:~> curl -i -v -u root:exo \
-X POST \
--data-binary @JcrGroovyTest.groovy \
-H 'Content-type:script/groovy' \
http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy
Node '/script/groovy/JcrGroovyTest.groovy' has property
exo:autoload=true so script will be re-deployed automatically when script source code changed.

Script was redeployed, now try access newly created method.
andrew@ossl:~> curl -u root:exo \
-X POST \
http://localhost:8080/rest/groovy/test/repository/production/test
Method excution should be quiet, without output, traces, etc.
Then we can try again get node UUID.
andrew@ossl:~> curl -u root:exo \
http://localhost:8080/rest/groovy/test/repository/production/test
1b8c88d37f0000020084433d3af4941f
Node UUID: 1b8c88d37f0000020084433d3af4941f
We don't need this scripts any more, so remove it from JCR.
andrew@ossl:~> curl -u root:exo \
http://localhost:8080/rest/script/groovy/delete/repository/production/script/groovy/JcrGroovyTest.groovy
7 Groovy script restrictions
You should keep one class per one groovy file. The same actually for interface and it implementation. It's limitation of groovy parser that does not have type Class[] parseClass(InputStream) or Collection
parseClass(InputStream) but only Class parseClass(InputStream) instead.
8 Dependencies resolving
Since WS version 2.2.0-Beta01 and JCR version 1.14.0-Beta01 there is support of script dependency resolving. If Groovy RESTful service use some classes from other Groovy scripts they can be loaded when service will be deployed. There is special org.exoplatform.container.component.ComponentPlugin for this (configuration example below).
<external-component-plugins>
<target-component>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</target-component>
<component-plugin>
<name>add groovy repo</name>
<set-method>addPlugin</set-method>
<type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScriptAddRepoPlugin</type>
<init-params>
<properties-param>
<name>repo1</name>
<property name="repository" value="repository"/>
<property name="workspace" value="production"/>
<property name="path" value="/dependencies"/>
</properties-param>
</init-params>
</component-plugin>
</external-component-plugins>
It is possible to add more then one dependencies location, just one more "properties-param" should be used.
When Groovy ClassLoader need load class in will try to load from repository "repository", workspace "production" and from folder "/dependencies/...". For example, we have simple Groovy RESTful service :
package org.exoplatform.groovy.test
import javax.ws.rs.GET
import javax.ws.rs.Path
import org.exoplatform.groovy.lib.Dependency1
@Path("groovy-test-dependency")
public class TestDependency {
@GET
def method() {
return new Dependency1().getName()
}
}
and other script
package org.exoplatform.groovy.lib
class Dependency1
{
String name = getClass().getName()
}
Script org.exoplatform.groovy.lib.Dependency1 MUST be located with respect to package name. So if we make a decision to store dependencies in repository "repository", workspace "production" and in folder "/dependencies" then full path to org.exoplatform.groovy.lib.Dependency1 must be /dependencies/org/exoplatform/groovy/lib/Dependency1.groovy. If script located as described it will be found automatically when "main" service will be deployed.
9 Security
Since WS version 2.2.0-Beta01 and JCR version 1.14.0-Beta01 it is possible to configure permissions of Groovy based RESTful services by using special codebase. Let's see how we allow a dynamic (possibly user defined) services to execute with limited permissions.
Add in existed or create new policy file and add next lines in it
grant codeBase "file:/groovy/script/jaxrs" {
};
In this case groovy services have not any privileges. Detailed list of JDK permissions available here http://java.sun.com/j2se/1.5.0/docs/guide/security/permissions.html#PropertyPermission
To give all permissions to Groovy services simply add next in policy file
grant codeBase "file:/groovy/script/jaxrs" {
permission java.security.AllPermission;
};
NOTE URL file:/groovy/script/jaxrs is virtual and not need be present on file system.
That is all.