Groovy Scripts as REST Services

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:

  1. Load script and save it in JCR.
  2. Instantiate script.
  3. Deploy newly created Class as RESTful service.
  4. Script Lifecycle Management.
  5. 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: console1.png

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.

console2.png

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

console3.png

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.

Tags:
Created by Andrey Parfonov on 12/08/2008
Last modified by Andrey Parfonov on 06/25/2010

Products

generated on Thu Sep 02 15:47:43 UTC 2010

eXo Optional Modules

eXo Core Foundations


Copyright (c) 2000-2010. All Rights Reserved - eXo platform SAS
2.4.30451