Support

Lorem ipsum dolor sit amet:

24h / 365days

We offer support for our customers

Mon - Fri 8:00am - 5:00pm (GMT +1)

Get in touch

Cybersteel Inc.
376-293 City Road, Suite 600
San Francisco, CA 94102

Have any questions?
+44 1234 567 890

Drop us a line
info@yourdomain.com

About us

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec.

Chapter 3. Basics

3. Basics

In the previous chapter we saw that ReportServer functionality is exposed by services. In this chapter we discuss the basics of scripting within ReportServer. This includes passing parameters to scripts, accessing various services, interpreting error messages, working with entities, reading and writing text files and more.

3.1. Executing Scripts

As we have seen, scripts in ReportServer are executed via the Terminal using the exec command. The exec command takes as command a script and passes on any further arguments to the script. You can access command line arguments in your script via the variable args. Suppose we adapt our hello world script as follows:

package myscripts

tout.println('Hello ' + args )

If we now execute the script as

exec helloworld.groovy John

You will get the output

Hello [John]

The square brackets around the name, indicate that the args variable is in fact an array, or rather a groovy collection. If we again change our script to

package myscripts

tout.println('Hello ' + args.reverse() )

and call it with

exec helloworld.groovy John Doe

the output will be

Hello [Doe, John]

3.1.1. Return Values

Scripts have return values. The return value will be output in the terminal. You can either explicitly return from a script via the usual "return x" instruction or else the last line of your script will be interpreted as return value. Thus, we could change our above script to

package myscripts

'Hello ' + args.reverse()

to get the same effect: an output in the terminal.

3.2. Executing Scripts via URL

Besides executing scripts from the terminal, you can execute scripts by calling a specific URL. To execute a script with ID 15 you need to call

http://SERVER:PORT/reportserverbasedir/reportserver/scriptAccess?id=15

The following parameters are available

id The script's id.
path Alternatively to specifying an id you can specify the path of a script. E.g.: http://SERVER:PORT/reportserverbasedir/reportserver/scriptAccess?path=/bin/myscript.groovy
args The arguments to be passed to the script.
exception In case of an exception the server will not respond with an error message. In order to make the server respond with the actual exception set this parameter to true.
commit Whether or not the script should be executed in commit mode.

If executing a script via URL you have access to the predefined variable httpRequest as well as httpResponse which provide access to the HTTP request and response object.

3.3. The GLOBALS Object

Every ReportServer script is initialized with a scope that contains an object called GLOBALS. This object exposes ReportServer functionality to your script. This allows you to access ReportServer services but it also provides certain functionality to scripts such as easily accessing files in the fileserver. The most common case is access of ReportServer services and to access entities.

Services can be retrieved via the method getInstance(). getInstance takes a class object as input and returns the corresponding service object. It is also possible to get access to a Provider object for a service. Providers can be regarded as containers which do not immediately access the service but which allow access to that particular service. This can for example be helpful when writing complex scripts and you want to give access to services to functions or classes. Providers can be accessed via the getProvider method which also takes a class object as input.

In the following we create a simple script that allows to search your ReportServer installation for users. For this we will use the SearchService located in package net.datenwerke.rs.search.service.search. The service exposes the search functionality provided by ReportServer.

  • Tip: In order to use auto completion and automatic import statements when editing scripts in Eclipse you must add all projects to the list of required projects. For this open the groovy project's properties (right-click on the project then choose properties) and Java Build Path. Choose the Projects tab and select all other projects.
  • Tip: Eclipse's auto-completion works better if you program Groovy using explicit types. That is, instead of writing
def var = 'string'
String var = 'string'

Create a new script in Eclipse called searchuser.groovy. We will require the SearchService from the GLOBALS object. We will then use the locate method to retrieve a list of results. As input we will simply pass on the argument array (converted to a string).

package myscripts

import net.datenwerke.rs.search.service.search.SearchService
import net.datenwerke.security.service.usermanager.entities.User

SearchService searchSvc = GLOBALS.getInstance(SearchService)
searchSvc.locate(User.class, args.join(" "))

If we add this script to our tmp folder in ReportServer and execute it via

exec searchuser.groovy root

you will get the output

[root root]

Let us create a second user via the user manager called "Tom Rootinger". If we run our script again with root as argument we will get the same output. This is because, by default the search service looks for exact matches. If we run the script as

exec searchuser.groovy root*

we get the expected result:

[root root, Tom Rootinger]

In a next step we want to use the HistoryService (located in net.datenwerke.gf.service.history) to generate links for the objects found by our script. The HistoryService has a single method that takes an object and returns a list of links (objects of type HistoryLink). We will simply take the first link in this list (if it exists) and output it. The adapted script looks like

package myscripts

import net.datenwerke.gf.service.history.HistoryService
import net.datenwerke.rs.search.service.search.SearchService
import net.datenwerke.security.service.usermanager.entities.User

SearchService searchSvc = GLOBALS.getInstance(SearchService)
HistoryService historySvc = GLOBALS.getInstance(HistoryService)

searchSvc.locate(User.class, args.join(" ")).collect{
	historySvc.buildLinksFor(it).get(0)?.getLink()
}.join("\n")

If we run this again using exec searchuser.groovy root* we get as output

usermgr/path:1.2.3&nonce:-1668828071
usermgr/path:1.4&nonce:475784900

On the one hand we are missing the server address and on the other, wouldn't it be nice to be able to actually click on the link? The server address can be accessed via the ReportServerService. Thus we could adapt our script as

package myscripts

import net.datenwerke.gf.service.history.HistoryService
import net.datenwerke.rs.core.service.reportserver.ReportServerService
import net.datenwerke.rs.search.service.search.SearchService
import net.datenwerke.rs.terminal.service.terminal.obj.CommandResult
import net.datenwerke.security.service.usermanager.entities.User

SearchService searchSvc = GLOBALS.getInstance(SearchService)
HistoryService historySvc = GLOBALS.getInstance(HistoryService)
String urlBase = GLOBALS.getInstance(ReportServerService).serverInfo.baseURL

searchSvc.locate(User.class, args.join(" ")).collect{
	  urlBase + historySvc.buildLinksFor(it).get(0)?.link
}

For the second part we need to return an object of type CommandResult (in package net.datenwerke.rs.terminal.service.terminal.obj). The CommandResult object allows us to better specify how the terminal treats the result returned by the script. It is able to display lists, tables, html and what we need for our task: links. The CommandResult object can create links either to external pages via addResultHyperLink() method or to internal locations via the addResultAnchor() method. Following is the final script.

package myscripts

import net.datenwerke.gf.service.history.HistoryService
import net.datenwerke.rs.core.service.reportserver.ReportServerService
import net.datenwerke.rs.search.service.search.SearchService
import net.datenwerke.rs.terminal.service.terminal.obj.CommandResult
import net.datenwerke.security.service.usermanager.entities.User

SearchService searchSvc = GLOBALS.getInstance(SearchService)
HistoryService historySvc = GLOBALS.getInstance(HistoryService)
String urlBase = GLOBALS.getInstance(ReportServerService).serverInfo.baseURL

CommandResult result = new CommandResult()
def links = searchSvc.locate(User.class, args.join(" ")).collect{
	  result.addResultHyperLink(it.getName(), historySvc.buildLinksFor(it).get(0)?.link )
}

return result

Tip: Via the terminal/alias.cf configuration file you can make scripts directly accessible. For example the entry would allow you to access your searchuser script from anywhere using the command search. If you change the config file don't forget to make ReportServer reload the configuration using the config reload terminal command.

<entry>
	<alias>search</alias>
	<command>exec /fileserver/bin/tmp/searchuser.groovy</command>
</entry>

3.4. Working with Entities

So far we have seen how we can access services via the GLOBALS object. In the following we will see how to work with entities. Entities are stored objects such as reports, users or TeamSpaces. You can find entities by searching for classes annotated with @Entity. You can also find a list of all entities in our ReportServer SourceForge project https://sourceforge.net/projects/dw-rs/. Download the latest apidocs file from the src directory for this.

Without directly spelling it out, we have in our above example already worked with user objects as the SearchService returns the actual entity objects. In the following we want to show how to access a particular entity by id. Almost all entities have a unique identifier which is usually called id and which normally can be accessed via the getId() method. Ids are in most cases of type Long. Go to the user manager in the administration module and select an arbitrary user (or use your searchuser script and click on a link). The user's id is displayed in the header line of the form that allows to set the user's properties. Let us assume that id is 4.

Usually, when you work with entities you will work with JPA's EntityManager (see http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html). The GLOBALS object provides access to an EntityManager by either the method getEntityManager() or as a Provider via getEntityManagerProvider(). For quick access to an entity it furthermore provides the methods findEntity() and getEntitiesByType(). In the following we want to create a simple script that displays a list of all user objects. In a first step, we only access the user object with id 4 and display its name. We name our script userlist.groovy.

package myscripts

import net.datenwerke.security.service.usermanager.entities.User

GLOBALS.findEntity(User, 4l)

Note the "l" behind the id. This is necessary to tell Groovy that the id is of type Long and not an integer. If you execute the script via

exec userlist.groovy

the user's name is printed. What the findEntity method returned however, was the user object. Let's have a closer look at that object. For this we can either have a look at the source of User (located in net.datenwerke.security.service.usermanager.entities), the javadoc of the User class or we use the terminal command desc which takes as argument an entity name and outputs a description of the entity class. Run

desc User

on the terminal. You see the various database fields that a User object stores. By convention, each field has a corresponding getter and setter. Thus the "username" property could be accessed via "getUsername()". Let us change our script to output the username.

package myscripts

import net.datenwerke.security.service.usermanager.entities.User

User user = GLOBALS.findEntity(User, 4l)

user.username + ' - ' + user.name

If we want to list the username of all users we can use the following adaption

package myscripts

import net.datenwerke.security.service.usermanager.entities.User;

GLOBALS.getEntitiesByType(User).collect{
	it.username
}.join("\n")

3.4.1. Writing to Entities

Suppose we want to do a bulk update, for example, synchronize our user objects with an external database. As you can imagine this is straightforward using ReportServer scripts. In the following we will add a simple comment to the description field of any group. For this, we can use a simple adaption of the above script

package myscripts

import net.datenwerke.security.service.usermanager.entities.Group

GLOBALS.getEntitiesByType(Group).each{
    it.description = "some description"
	tout.println("changed group: " + it.name)
}

tout.println("done")

We call the file editgroups.groovy. To test our script, first go the the user management module in the admin module and create a few groups, let's say groups A and B. If you execute the script via exec editgroups.groovy you should see the following output:

changed group: A
changed group: B
done

However, if you look at your groups A and B nothing has changed, that is, the description was not added. This is because, by default the database transaction spanning a script execution is rolled backed after the script is finished. In order to commit the transaction you need to supply the flag "-c" to the exec command. Call your script via

exec -c editgroups.groovy

If you now inspect the groups in the user management area you will find that, indeed, the description was added. Instead of inspecting the group in the user manager you can do this directly from the terminal via the desc command. As a third parameter the desc command takes an object which is then displayed. Thus, you could, for example, write

desc Group /usermanager/A

if your group is located directly in the root directory. Alternatively, you can specify the object via an hql (hibernate query language) query. Thus, we could also access the Group with name "A" via

desc Group "hql:from Group where name='A'"

In this way, you can also display multiple objects side by side

desc Group "hql:from Group"

To display the result in a new window, that is, the result is not directly added to the terminal window, add the -w flag to the desc command. To complete the picture you can also specify objects on the terminal via type and id. Thus, if your group has id 6, you could also write

desc Group id:Group:6

3.5. Interpreting Script Error Messages

When developing scripts ReportServer will support your debugging efforts by showing you full stack trace error messages. Lets go back to our simple userlist script but this time, I have included a typo:

package myscripts

import net.datenwerke.security.service.usermanager.entities.User;

GLOBALS.getEntitiesByType(User).collect{
	it.username
}.join("\n")

If we name this file "errorfile.groovy" and execute it, you will get the following error message:

net.datenwerke.rs.scripting.service.scripting.exceptions.ScriptEngineException: javax.script.ScriptException: javax.script.ScriptException: groovy.lang.MissingPropertyException: No such property: i for class: myscripts.Script5
at net.datenwerke.rs.scripting.service.scripting.engines.GroovyEngine.eval(GroovyEngine.java:62)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:199)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:242)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:285)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:256)
at net.datenwerke.rs.scripting.service.scripting.terminal.commands.ExecScriptCommand$1$1.doFilter(ExecScriptCommand.java:228)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:66)
at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:168)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
at net.datenwerke.rs.scripting.service.scripting.terminal.commands.ExecScriptCommand$1.call(ExecScriptCommand.java:220)
at net.datenwerke.rs.scripting.service.scripting.terminal.commands.ExecScriptCommand$1.call(ExecScriptCommand.java:1)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.lang.Thread.run(Thread.java:722)
Caused by: javax.script.ScriptException: javax.script.ScriptException: groovy.lang.MissingPropertyException: No such property: i for class: myscripts.Script5
at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:138)
at net.datenwerke.rs.scripting.service.scripting.engines.GroovyEngine.eval(GroovyEngine.java:60)
... 15 more
Caused by: javax.script.ScriptException: groovy.lang.MissingPropertyException: No such property: i for class: myscripts.Script5
at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:335)
at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:132)
... 16 more
Caused by: groovy.lang.MissingPropertyException: No such property: i for class: myscripts.Script5
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:50)
at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:231)
at myscripts.Script5$_run_closure1.doCall(Script5.groovy:6)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:904)
at groovy.lang.Closure.call(Closure.java:415)
at groovy.lang.Closure.call(Closure.java:428)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:2203)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:2173)
at org.codehaus.groovy.runtime.dgm$60.invoke(Unknown Source)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at myscripts.Script5.run(Script5.groovy:5)
at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:332)
... 17 more

The error stack trace typically consists of three parts: the error message, in this case

net.datenwerke.rs.scripting.service.scripting.exceptions.ScriptEngineException: javax.script.ScriptException: javax.script.ScriptException: groovy.lang.MissingPropertyException: No such property: i for class: myscripts.Script5

a stack trace of the failure propagation through ReportServer, here

at net.datenwerke.rs.scripting.service.scripting.engines.GroovyEngine.eval(GroovyEngine.java:62)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:199)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:242)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:285)
at net.datenwerke.rs.scripting.service.scripting.ScriptingServiceImpl.executeScript(ScriptingServiceImpl.java:256)
at net.datenwerke.rs.scripting.service.scripting.terminal.commands.ExecScriptCommand$1$1.doFilter(ExecScriptCommand.java:228)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:66)
at com.google.inject.servlet.FilterDefinition.doFilter(FilterDefinition.java:168)
at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:58)
at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
at net.datenwerke.rs.scripting.service.scripting.terminal.commands.ExecScriptCommand$1.call(ExecScriptCommand.java:220)
at net.datenwerke.rs.scripting.service.scripting.terminal.commands.ExecScriptCommand$1.call(ExecScriptCommand.java:1)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.lang.Thread.run(Thread.java:722)

and the actual exception, which starts with a caused by clause:

Caused by: groovy.lang.MissingPropertyException: No such property: i for class: myscripts.Script5
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:50)
at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:231)
at myscripts.Script5$_run_closure1.doCall(Script5.groovy:6)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:904)
at groovy.lang.Closure.call(Closure.java:415)
at groovy.lang.Closure.call(Closure.java:428)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:2203)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:2173)
at org.codehaus.groovy.runtime.dgm$60.invoke(Unknown Source)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at myscripts.Script5.run(Script5.groovy:5)
at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:332)

Typically, what you need to look at to understand what went wrong is the caused by clause. In this case it tells us that the property "i" does not exist. It also tells us, how the script is known internally. It is called myscripts.Scripts where myscripts is the package name that we specified. If we examine the stack trace further we find in line 5 the following statement

at myscripts.Script5$_run_closure1.doCall(Script5.groovy:6)

This was the last call executed in our script. The number after the colon specifies the line that was executed. Thus, we should look for the missing property in line 6 of our script. As line 6 read

i.getUsername()

which should have been it instead of i, we have traced the error to its origin.

3.6. Reading and Writing to Files

Next we describe how to write to files (in the internal filesystem) from a script. Basically there are two ways to accomplish this. We have already seen how to work with persistent objects (or rather entities) such as users. A file is just such an entity:

net.datenwerke.rs.fileserver.service.fileserver.entities.FileServerFile

The corresponding service is the FileServerService. Thus, we could create a new file and store it. There is, however, a simpler way using the GLOBALS object. The GLOBALS object comes with a dedicated fileService object, that allows to easily read from and write to files.

As a first step we construct a new text file. We open the terminal and go to the fileserver and create a new temporary folder (e.g. tmp with mkdir tmp). To write some text into a new file we can simply use the following terminal command

echo foobar > file.txt

The echo command prints its arguments and the angular opening bracket forwards this output into the file file.txt. A single angular bracket will overwrite the contents of the file. To append text to the file use two angular brackets, for example:

echo more foobar >> file.txt

To inspect the created file you can either use the editTextFile command or use the "cat" command which simply dumps the text file to the terminal.

cat file.txt

Next, we are going to write a simple script that is going to emulate the cat command. We will call this script myCat.rs. For this create the script using

createTextFile myCat.groovy

Remember that scripts need to be located beneath the bin folder. So if you created your script outside of the bin hierarchy move it there using the mv command. To emulate the cat command we only need a single line as the GLOBALS object offers some helper methods to read in files.

GLOBALS.read(args[0])

The read method takes a location of a file and returns the contents of the file as a string. Similarly the write method of the GLOBALS object allows to write to a text file. Note that this will overwrite the data within the file. Thus the following script emulates the terminal command "echo TEXT > file"

GLOBALS.write(args[0], args[1])

This could then be called, for example as

exec -c myWrite.groovy file.txt 'This is some text'

Note the use of the -c flag to commit the changes and the use of single quotes to treat 'This is some text' as a single argument.

The read and write method of the GLOBALS object will always return (or expect) a string. If you want to work with binary files you can use the fileService. Via the globals object you have access to all Services provided by ReportServer. Additionally, there are a couple of services specifically for working with scripts. These are accessed via the services variable of the GLOBALS object. That is, we could have written our myCat.groovy script also as

GLOBALS.services['fileService'].read(args[0])

In addition to the read and write methods the fileService has additionally the methods readRaw and writeRaw that allow to work directly with byte representations of the files.

Besides the fileService there are other services specifically available for script developers to help facilitate writing scripts. We will encounter them in the course of this manual. However, here is a short overview of the services available via GLOBALS.services

fileService easy access to read and write files.
scriptService provides methods to call scripts from within your script.
registry A singleton registry that can be used to store and retrieve arbitrary values. The registry is held in memory and cleared on ReportServer restarts.
clientExtensionService An interface to access client side extensions.

3.7. Nesting Scripts: Calling Scripts from Scripts

To properly structure larger scripts it can be helpful to spread functionality over several scripts. The above mentioned scriptService provides simple helper methods to call scripts from within scripts. For this, consider the following script which does nothing but provide two methods

def caesarEncode(k, text) {
    (text as int[]).collect { it==' ' ? ' ' : (((it & 0x1f) + k - 1).mod(26) + 1 | it & 0xe0) as char }.join()
}
def caesarDecode(k, text) { caesarEncode(26 - k, text) }

Assume this is stored in a file called lib.groovy. For the interested reader, this is a simple implementation of the Caesar cipher found here: https://www.rosettacode.org/wiki/Caesar_cipher#Groovy (note this should not be used for actual encryption). Now, to load these helper methods we can use the following, which we store in a file called nestingTest.groovy

GLOBALS.exec('lib.groovy')

def plain = 'The quick brown fox jumps over the lazy dog'
def key = 6
def cipher = caesarEncode(key, plain)

tout.println plain
tout.println cipher
tout.println caesarDecode(key, cipher)

If you now run this script you should get the following output

reportserver$ exec nestingTest.groovy
The quick brown fox jumps over the lazy dog
Znk waoiq hxuct lud pasvy ubkx znk rgfe jum
The quick brown fox jumps over the lazy dog

The example above can be found here: https://github.com/infofabrik/reportserver-samples/tree/main/src/net/datenwerke/rs/samples/tools/nesting.

  • Note that ReportServer uses internal script caching for performance optimization. When developing nested scripts, if you get an old script version when running, you can manually clear scripting cache with the clearInternalScriptCache terminal command.
  • Note you can use relative paths for lib.groovy. For example:

GLOBALS.exec('../crypt/libs/lib.groovy')

or

GLOBALS.exec('libs/lib.groovy')

Besides the simple exec method that we have used above the GLOBALS object provides a second exec method that takes as second parameter an argument string that is passed to the script.

3.7.1. Using classes in nested scripts

When you need to use classes in nested scripts, you can not use the

GLOBALS.exec('../crypt/libs/lib.groovy')

method explained above directly. With other words, this would not work if B contains a class definition:

GLOBALS.exec('/fileserver/bin/B.groovy')
def b = new B()
b.prepareString()

Groovy would try to compile your script, but cannot find B's class definition during compilation time.

In this case, you can use the methods GLOBALS.loadClass() and GLOBALS.loadClasses() for loading your class definitions. Using GLOBALS.newInstance() allows you to create instances as shown below. Finally, you can call your instance's methods, in this example prepareString().

def bClass = GLOBALS.loadClass('B.groovy', 'net.datenwerke.rs.samples.tools.nesting.nestedclass.B')
def bInstance = GLOBALS.newInstance(bClass)
return bInstance.prepareString()"

A complete example using two levels (A.groovy creating B objects creating C objects) can be found here: https://github.com/infofabrik/reportserver-samples/tree/main/src/net/datenwerke/rs/samples/tools/nesting/nestedclass.

The following example shows how to load classes which are defined in the same .groovy file (B and C are defined both in myLibraries.groovy): https://github.com/infofabrik/reportserver-samples/tree/main/src/net/datenwerke/rs/samples/tools/nesting/multipleclass

InfoFabrik GmbH

Wir wollen, dass alle Unternehmen, Institutionen und Organisationen, die Daten auswerten, selbständig und zeitnah genau die Informationen erhalten, die sie für ein erfolgreiches Arbeiten benötigen.

InfoFabrik GmbH
Klingholzstr. 7
65189 Wiesbaden
Germany

+49 (0) 611 580 66 25

Kontaktieren Sie uns

Was ist die Summe aus 1 und 4?
Copyright 2007 - 2024 InfoFabrik GmbH. All Rights Reserved.

Auf unserer Website setzen wir Cookies und andere Technologien ein. Während einige davon essenziell sind, dienen andere dazu, die Website zu verbessern und den Erfolg unserer Kampagnen zu bewerten. Bei der Nutzung unserer Website werden Daten verarbeitet, um Anzeigen und Inhalte zu messen. Weitere Informationen dazu finden Sie in unserer Datenschutzerklärung. Sie haben jederzeit die Möglichkeit, Ihre Einstellungen anzupassen oder zu widerrufen.

Datenschutzerklärung Impressum
You are using an outdated browser. The website may not be displayed correctly. Close