eXo Platform 3.5

## Chapter 88. Groovy Scripts as REST Services

## 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.

## 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.

Load script at startup time

This way can be used for load prepared scripts, to 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>

The first is value-param sets JCR repository, the second is value-param sets workspace and the third one is sets JCR node where scripts from plugin will be stored. If specified node does not exist, 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.

Load script via HTTP

This is samples of HTTP requests. In this example, 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

## 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>

## Deploying newly created Class as RESTful service

To 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).

## Script Lifecycle Management

If GroovyScript2RestLoader configured as was decribed in the previous section, then all "autoload" scripts deployed. In the 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 imitates 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

## Getting 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 the server. If configuration is correct and script does not have syntax error, you should see next:

In the screenshot, we can see the service deployed.

First, create a folder via WebDAV in the 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 to access a 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

## 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.

That is all.