Groovy Script Service Tutorial

Version 20.1 by Paul Libbrecht on 2019/06/26 08:04

A Script Service is a XWiki Components and the recommended approach for writing XWiki Components is either to write them in Java or to write them in wiki pages using Wiki Components.

However, there's currently a known limitation in the Wiki Component feature that prevents using it for writing Script Service components. That should leave you with the Java option which is the recommended approach. However, for various reasons, you may not want to use Java and you may still want to write them in wiki pages (some don't like Java or don't want to go in Java development or want to have a fast turnaround time, etc).

You'll learn below how to develop a Script Service, in a wiki page, using Groovy. However, the technique demonstrated will work for any scripting language and not just for Script Service. It's a way to be able to execute any script once in your wiki and be guaranteed that whenever your wiki restarts, your script also executes! More precisely the script will execute:

  • When the wiki starts
  • Whenever the wiki page we will create is modified

Prerequisites

Before taking this tutorial you should be familiar with:

It's also better if you have some understanding of XWiki Components and Event Listeners but it's not mandatory.

Generally speaking, the approach below is quite complex and requires a good level of understanding of the XWiki internals. Not for the faint of hearts! emoticon_smile

You will also need programming right to use wiki components and Groovy.

The Concept

  • Create a wiki page and transform it into a Wiki Component
  • This Component will only serve as a way to define our Script Service or execute our script. It won't be used for anything else.
  • The trick is that we'll have this Component implement the Initializable interface (and thus implement an initialize() method which is where we will define our Script Service or execute our script).
  • To understand why this will work, you need to understand how the Wiki Component feature of XWiki works:
    • When a page containing a wiki component is created or modified, a Java component is created dynamically and registered against XWiki's Component Manager. To do this, the Component is instantiated (using a Java Dynamic Proxy) and this instance is registered. When a Component is instantiated, if it implements the Initializable interface, its initialize() method is called.
    • When the wiki starts, the Wiki Component feature is initialized and it looks for all wiki pages defining wiki components and registers them. And thus it instantiates the Components, and thus our initialize() method is also called.

One last piece of information: Below we use an Event Listener type of Wiki Component but in practice it could be any Component Role.

Let's get started!

Create a Wiki Component Event Listener

  • Create a wiki page, e.g. HelloWorldGroovyScriptService
  • Add an object of class Wiki Component XWiki Class (XWiki.ComponentClass) to it, with the following properties:
    • Role type: org.xwiki.observation.EventListener
    • Role hint: helloWorldGroovyScriptService
    • Scope: Current Wiki

Turn the Component into an Initializable

  • Add an object of class Wiki Component Implements Interface XWiki Class (XWiki.ComponentInterfaceClass) to the page, with the following property:
    • Interface qualified name: org.xwiki.component.phase.Initializable

Add the methods to the component

We now need to implements all the methods of the EventListener and Initializable interfaces.

Some important remarks concerning the EventListener methods:

  • Since the Event Listener won't be used as a real listener, we don't want it to listen to anything at all, which is why we return an empty array for its getEvents method.
  • Since the Event Listener won't listen to any event, we don't even need to implement the onEvent method that Event Listeners are supposed to implement!
  • Thus we only need to implement the getName (to give it a unique Component hint) and getEvents methods

Follow these steps:

  • Add an object of class Wiki Component Method XWiki Class (XWiki.ComponentMethodClass) to the page, with the following properties:
    • Name: initialize
    • Body code:
      {{groovy}}
      import javax.inject.Named
      import javax.inject.Singleton

      import org.xwiki.component.annotation.Component
      import org.xwiki.component.annotation.ComponentAnnotationLoader
      import org.xwiki.script.service.ScriptService

      @Component
      @Named("helloWorld")
      @Singleton
      public class HelloWorldGroovyScriptService implements ScriptService
      {
         public String execute()
         {
             return "Hello world from Groovy script service"
         }
      }

      // Note: we get the Component Manager for the current wiki since in our example we want to register our Script Service
      // Component only in the current wiki. We could as well register it in the Root Component Manager for all wikis.
      def componentManager = services.component.getComponentManager('wiki:' + services.wiki.currentWikiId)

      // Parse the annotations of the class above to generate a Component Descriptor to register the class as a Component in the Component Manager.
      def loader = new ComponentAnnotationLoader()
      def descriptors = loader.getComponentsDescriptors(HelloWorldGroovyScriptService.class)

      // Note: Annotations can define several descriptors (by implementing several roles) so we iterate over all of them and register the Component
      for (descriptor in descriptors) {
          componentManager.registerComponent(descriptor)
      }
      {{/groovy}}

      You could of course use any script here in any supported Scripting language. You could also use an {{include/}} macro macro to let the script execution read code from another page.

  • Add the following method to the component, to make sure the listener has a unique name, e.g. the current page reference converted to a string:
    • Name: getName
    • Body code:
      {{groovy}}
      xcontext.method.output.value = doc.documentReference.toString()
      {{/groovy}}
  • Add the method below to the component. As we discussed we don't want to listen to any events, and we return an empty array
    • Name: getEvents
    • Body code:
      {{groovy}}
      xcontext.method.output.value = []
      {{/groovy}}

Test the script service

In a distinct page, enter the code below and make sure that you get the hello world output.

{{velocity}}
$services.helloWorld.execute()
{{/velocity}}
Tags:
    

Get Connected