Dynamic Language Tasks
Event Driven Remoting and Executor Based Remoting allow you to simply expose and execute custom services using the Space as the transport layer. The dynamic tasks feature (also known as "scripting support") allows you to utilize a built remote service, exposed using these two remoting mechanisms, that can execute dynamic language scripts in the space. A major benefit of using scripting or dynamic languages is the ability to simply change the behavior without the need to recompile your application. As per the SBA paradigm, dynamic language scripts can be executed collocated with the Space, allowing you to execute the task as close as possible to the data it needs to access, and then return a result.
The following section goes through the steps needed in order to execute dynamic language tasks. The other sections in this page dive more deeply into different dynamic language support features.
The first step in using scripting is exposing the built in scripting service over the Space. Here is a simple example:
<!-- The scripting built in service executor -->
<bean id="scriptingExecutor" class="org.openspaces.remoting.scripting.DefaultScriptingExecutor" />
<!-- The service exporter exposing the scripting service -->
<os-remoting:service-exporter id="serviceExporter">
<os-remoting:service ref="scriptingExecutor"/>
</os-remoting:service-exporter>
<os-core:embedded-space id="space" space-name="mySpace"
<os-core:giga-space id="gigaSpace" space="space"/>
<!-- Polling container exposing the service exporter as async remoting -->
<os-events:polling-container id="eventContainer" giga-space="gigaSpace">
<os-events:listener ref="serviceExporter">
</os-events:polling-container>
<!-- The scripting built in service executor -->
<bean id="scriptingExecutor" class="org.openspaces.remoting.scripting.DefaultScriptingExecutor" />
<!-- The service exporter exposing the scripting service -->
<bean id="serviceExporter" class="org.openspaces.remoting.SpaceRemotingServiceExporter">
<property name="services">
<list>
<ref bean="scriptingExecutor" />
</list>
</property>
</bean>
<bean id="space" class="org.openspaces.core.space.EmbeddedSpaceFactoryBean">
<property name="name" value="space" />
</bean>
<bean id="gigaSpace" class="org.openspaces.core.GigaSpaceFactoryBean">
<property name="space" ref="space" />
</bean>
<!-- Polling container exposing the service exporter as async remoting -->
<bean id="eventContainer" class="org.openspaces.events.polling.SimplePollingEventListenerContainer">
<property name="eventListener" ref="serviceExporter">
</bean>
The next simple step is using the exposed scripting service on the client side, here is a groovy example :
public class MyRemoting {
@EventDrivenScriptingExecutor
private ScriptingExecutor asyncScriptingExecutor;
@ExecutorScriptingExecutor
private ScriptingExecutor execScriptingExecutor;
public void doSomeScripting() {
asyncScriptingExecutor.execute(new StaticScript("myScriptAsync", "groovy",
"println 'Async Hello Groovy'"));
execScriptingExecutor.execute(new StaticScript("myScriptExec", "groovy",
"println 'Sync Hello Groovy'"));
}
}
On the client side Spring XML configuration, the following needs to be defined (mainly to define the connection to the Space as the transport layer, and define the scripting annotation processor):
<os-core:space-proxy id="space" space-name="mySpace"/>
<os-core:giga-space id="gigaSpace" space="space"/>
<os-remoting:annotation-support />
<bean id="myRemoting" class="MyRemoting" />
<bean id="space" class="org.openspaces.core.space.SpaceProxyFactoryBean">
<property name="name" value="space" />
</bean>
<bean id="gigaSpace" class="org.openspaces.core.GigaSpaceFactoryBean">
<property name="space" ref="space" />
</bean>
<bean id="remotingAnnotationSupport" class="org.openspaces.remoting.RemotingAnnotationBeanPostProcessor" />
<bean id="myRemoting" class="MyRemoting" />
What is a Script
Script
is a simple interface that is defined within the OpenSpaces scripting over remoting support. It has the following basic methods:
public interface Script extends BroadcastIndicator {
/**
* The name of the script. Should uniquely identify scripts.
*/
String getName();
/**
* The type of the script (or actually, language). For example, <code>groovy</code> or <code>ruby</code>.
*/
String getType();
/**
* The script source as a string.
*/
String getScriptAsString();
/**
* One or more parameters that will be passed to the script.
*/
Map<String, Object> getParameters();
/**
* The routing index for the script. Can be <code>null</code>. Defaults
* to a random routing.
*/
Object getRouting();
/**
* Should this script be cached or not. Defaults to <code>true</code>.
*/
boolean shouldCache();
/**
* Returns a set of meta arguments that are used for remote invocation.
*
* @see org.openspaces.remoting.SpaceRemotingInvocation#getMetaArguments()
*/
Object[] getMetaArguments();
}
There are several implementations of this class which is covered more in the client section. OpenSpaces comes with three different implementations of the Script
interface, they are:
StaticScript
: A script that holds the actual script as a string. The name, type, and script must be provided.StaticResourceScript
: A static script that uses SpringResource
andResourceLoader
to load a given script (for example, from the classpath).ResourceLazyLoadingScript
: A lazy loading script that uses Spring abstraction on top of a resource. The script resource location uses Spring notations for location (similar to a URL, with the addition of theclasspath:
prefix).
Exposing JavaScript Scripting Service
OpenSpaces scripting over remoting support has a built in service that allows to expose the execution of scripting code. The service is implemented using the DefaultScriptingExecutor
class. The executor supports several scripting languages out of the box, allows to cache compiled scripts, and automatically expose common beans as script parameters.
Local Script Executor
The DefaultScriptingExecutor
holds one or more LocalScriptExecutor
. A LocalScriptExecutor
implementation is an abstraction on top of an actual scripting language (such as groovy or jruby). OpenSpaces scripting support comes with built in local executors for groovy, jruby, and Java 6 ScriptEngine
abstraction.
By default, the default scripting executor identifies if groovy, jruby, or Java 6 scripting support exist within the classpath, and automatically registers then as possible local executors. The relevant local executors are GroovyLocalScriptExecutor
for groovy, JRubyLocalScriptExecutor
for jruby, and Jsr223LocalScriptExecutor
for Java 6. The local scripting executor is chosen based on the Script#getType()
.
The Java 6 local script executor is a special case as in-itself it allows to execute several scripting engines types. The DefaultScriptingExecutor
handles that by trying to offload the execution of an unidentified type to the Jsr223LocalScriptExecutor
. Unidentified type is one that has no specific LocalScriptExecutor
configured for it within the DefaultScriptingExecutor
. This means that if one wishes to execute a JavaScript script that comes built in with Java 6, the type for the script should be JavaScript
(and not Jsr223).
Script Execution Parameters
The Script
interface allows to define specific parameters per script. The DefaultScriptingExecutor
can be configured with a set of parameters that is passed to each script execution. A setParameters
that accepts a Map can be passed to each script.
By default, the DefaultScriptingExecutor
passes beans of type GigaSpace
, keyed by their bean id. The Spring ApplicationContext
is passed to the script under the applicationContext
key, and the ClusterInfo
object is passed under the clusterInfo
key.
As an example, if my server side Spring application context has a GigaSpace
bean with id of myGigaSpace
, a simple groovy script to use it (without configuring anything within the default executor) can be: myGigaSpace.write(new Object())
. Note, we use the id of the bean to access the GigaSpace
and write a new Object.
A quick note regarding usage of GigaSpace
beans. The clustered flag plays an important role when using it. This basically allows to control if the script execution of GigaSpace
operations works only against its clustered member space, or against the whole cluster.
Script Compilation Caching
If a scripting engine support compilation of scripting, the DefaultScriptingExecutor
allows caching of compiled scripts for faster execution. The actual caching of scripts is abstracted using CompiledScriptCache
interface, and has default LRU type cache built in.
The compilation is done based on a flag on the Script
interface (which defaults to true), and if cached, the "key" of the cache is the script name. This means that if a script was changed, it needs to be executed with a different script name in order for it to recompile and take affect.
Scripting on the Client Side
Using scripting on the client side is very similar to how similar remoting services are used. There are two types of proxies, executor-proxy
and event-driven-proxy
, and there are specific scripting annotations that allow to simplify the injection of scripting proxies.
Here is the xml definition of an async scripting proxy (the sync proxy is exactly the same except for the tag/class name of the proxy):
<os-remoting:event-driven-proxy id="eventDrivenScriptingExecutor" giga-space="gigaSpace"
interface="org.openspaces.remoting.scripting.ScriptingExecutor">
<os-remoting:aspect>
<bean class="org.openspaces.remoting.scripting.LazyLoadingRemoteInvocationAspect" />
</os-remoting:aspect>
<os-remoting:routing-handler>
<bean class="org.openspaces.remoting.scripting.ScriptingRemoteRoutingHandler" />
</os-remoting:routing-handler>
<os-remoting:meta-arguments-handler>
<bean class="org.openspaces.remoting.scripting.ScriptingMetaArgumentsHandler" />
</os-remoting:meta-arguments-handler>
</os-remoting:event-driven-proxy>
<bean id="eventDrivenScriptingExecutor" class="org.openspaces.remoting.EventDrivenSpaceRemotingProxyFactoryBean">
<property name="gigaSpace" ref="gigaSpace" />
<property name="serviceInterface" value="org.openspaces.remoting.scripting.ScriptingExecutor" />
<property name="remoteInvocationAspect">
<bean class="org.openspaces.remoting.scripting.LazyLoadingRemoteInvocationAspect" />
</property>
<property name="remoteRoutingHandler">
<bean class="org.openspaces.remoting.scripting.ScriptingRemoteRoutingHandler" />
</property>
<property name="metaArgumentsHandler">
<bean class="org.openspaces.remoting.scripting.ScriptingMetaArgumentsHandler" />
</property>
</bean>
Asynchronous scripting executor:
GigaSpace gigaSpace = new GigaSpaceConfigurer(new SpaceProxyConfigurer("mySpace")).gigaSpace();
ScriptingExecutor executor = new EventDrivenScriptingProxyConfigurer (gigaSpace).scriptingExecutor();
Executor based scripting executor:
GigaSpace gigaSpace = new GigaSpaceConfigurer(new SpaceProxyConfigurer("mySpace")).gigaSpace();
ScriptingExecutor executor = new ExecutorScriptingProxyConfigurer (gigaSpace).scriptingExecutor();
The different components implemented by scripting basically enables all the different scripting client side features which we will cover in the next sections. The annotation based injection is a much simplified way to use scripting on the client side and is shown in the Overview section.
Routing Index
The Script
interface allows to set a routing index which controls, both in async and sync mode (not in broadcast mode), where the execution of the script is directed. Here is a simple example of it:
asyncScriptingExecutor.execute(new StaticScript("myScript", "groovy","println 'Hello Groovy'").routing(1));
This causes the routing to go the the second member (assuming hash based routing is configured). Naturally, most often a business level routing will be used, for example, direct a script that executed on a certain share to a partition based on the share symbol.
Lazy Loading of Scripts
The StaticResourceScript
holds the actual script contents and it gets passed for each execution of the script. If script supports caching, there is no need to pass the actual contents of the script on each execution, since once the script was compiled, only its parameters and other values are required, not the actual script. The LazyLoadingRemoteInvocationAspect
aspect (on the client side) with the ResourceLazyLoadingScript
script type allow for such behavior. As an example:
asyncScriptingExecutor.execute(new ResourceLazyLoadingScript("test4", "groovy", "classpath:test.groovy").cache(true));
The above code only loads the test.groovy
file if it is required on the server side. By default it is not sent, and if the server has not compiled it, it returns to the client and request the actual script content. The script is then loaded and sent again to the server. This logic is implemented by the LazyLoadingRemoteInvocationAspect
.