migrate branch for lang-javascript

This commit is contained in:
Simon Willnauer 2015-06-09 15:17:27 +02:00
commit 64fdb0eeb0
16 changed files with 2433 additions and 0 deletions

View File

@ -0,0 +1,177 @@
JavaScript lang Plugin for Elasticsearch
==================================
The JavaScript language plugin allows to have `javascript` (or `js`) as the language of scripts to execute.
In order to install the plugin, simply run:
```sh
bin/plugin install elasticsearch/elasticsearch-lang-javascript/2.5.0
```
You need to install a version matching your Elasticsearch version:
| elasticsearch | JavaScript Plugin | Docs |
|---------------|-----------------------|------------|
| master | Build from source | See below |
| es-1.x | Build from source | [2.6.0-SNAPSHOT](https://github.com/elasticsearch/elasticsearch-transport-thrift/tree/es-1.x/#version-260-snapshot-for-elasticsearch-1x) |
| es-1.5 | 2.5.0 | [2.5.0](https://github.com/elastic/elasticsearch-lang-javascript/tree/v2.5.0/#version-250-for-elasticsearch-15) |
| es-1.4 | 2.4.1 | [2.4.1](https://github.com/elasticsearch/elasticsearch-lang-javascript/tree/v2.4.1/#version-241-for-elasticsearch-14) |
| es-1.3 | 2.3.1 | [2.3.1](https://github.com/elasticsearch/elasticsearch-lang-javascript/tree/v2.3.1/#version-231-for-elasticsearch-13) |
| es-1.2 | 2.2.0 | [2.2.0](https://github.com/elasticsearch/elasticsearch-lang-javascript/tree/v2.2.0/#javascript-lang-plugin-for-elasticsearch) |
| es-1.1 | 2.1.0 | [2.1.0](https://github.com/elasticsearch/elasticsearch-lang-javascript/tree/v2.1.0/#javascript-lang-plugin-for-elasticsearch) |
| es-1.0 | 2.0.0 | [2.0.0](https://github.com/elasticsearch/elasticsearch-lang-javascript/tree/v2.0.0/#javascript-lang-plugin-for-elasticsearch) |
| es-0.90 | 1.4.0 | [1.4.0](https://github.com/elasticsearch/elasticsearch-lang-javascript/tree/v1.4.0/#javascript-lang-plugin-for-elasticsearch) |
To build a `SNAPSHOT` version, you need to build it with Maven:
```bash
mvn clean install
plugin --install lang-javascript \
--url file:target/releases/elasticsearch-lang-javascript-X.X.X-SNAPSHOT.zip
```
Using javascript with function_score
------------------------------------
Let's say you want to use `function_score` API using `javascript`. Here is
a way of doing it:
```sh
curl -XDELETE "http://localhost:9200/test"
curl -XPUT "http://localhost:9200/test/doc/1" -d '{
"num": 1.0
}'
curl -XPUT "http://localhost:9200/test/doc/2?refresh" -d '{
"num": 2.0
}'
curl -XGET "http://localhost:9200/test/_search?pretty" -d '
{
"query": {
"function_score": {
"script_score": {
"script": "doc[\"num\"].value",
"lang": "javascript"
}
}
}
}'
```
gives
```javascript
{
// ...
"hits": {
"total": 2,
"max_score": 4,
"hits": [
{
// ...
"_score": 4
},
{
// ...
"_score": 1
}
]
}
}
```
Using javascript with script_fields
-----------------------------------
```sh
curl -XDELETE "http://localhost:9200/test"
curl -XPUT "http://localhost:9200/test/doc/1?refresh" -d'
{
"obj1": {
"test": "something"
},
"obj2": {
"arr2": [ "arr_value1", "arr_value2" ]
}
}'
curl -XGET "http://localhost:9200/test/_search" -d'
{
"script_fields": {
"s_obj1": {
"script": "_source.obj1", "lang": "js"
},
"s_obj1_test": {
"script": "_source.obj1.test", "lang": "js"
},
"s_obj2": {
"script": "_source.obj2", "lang": "js"
},
"s_obj2_arr2": {
"script": "_source.obj2.arr2", "lang": "js"
}
}
}'
```
gives
```javascript
{
// ...
"hits": [
{
// ...
"fields": {
"s_obj2_arr2": [
[
"arr_value1",
"arr_value2"
]
],
"s_obj1_test": [
"something"
],
"s_obj2": [
{
"arr2": [
"arr_value1",
"arr_value2"
]
}
],
"s_obj1": [
{
"test": "something"
}
]
}
}
]
}
```
License
-------
This software is licensed under the Apache 2 license, quoted below.
Copyright 2009-2014 Elasticsearch <http://www.elasticsearch.org>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>elasticsearch-lang-javascript</artifactId>
<packaging>jar</packaging>
<name>Elasticsearch JavaScript language plugin</name>
<description>The JavaScript language plugin allows to have javascript as the language of scripts to execute.</description>
<parent>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-plugin</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<properties>
<!-- You can add any specific project property here -->
</properties>
<dependencies>
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino</artifactId>
<version>1.7R4</version>
<exclusions>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<assembly>
<id>plugin</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<useTransitiveFiltering>true</useTransitiveFiltering>
<excludes>
<exclude>org.elasticsearch:elasticsearch</exclude>
</excludes>
</dependencySet>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>org.mozilla:rhino</include>
</includes>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -0,0 +1,44 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugin.javascript;
import org.elasticsearch.plugins.AbstractPlugin;
import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.script.javascript.JavaScriptScriptEngineService;
/**
*
*/
public class JavaScriptPlugin extends AbstractPlugin {
@Override
public String name() {
return "lang-javascript";
}
@Override
public String description() {
return "JavaScript plugin allowing to add javascript scripting support";
}
public void onModule(ScriptModule module) {
module.addScriptEngine(JavaScriptScriptEngineService.class);
}
}

View File

@ -0,0 +1,311 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.*;
import org.elasticsearch.script.javascript.support.NativeList;
import org.elasticsearch.script.javascript.support.NativeMap;
import org.elasticsearch.script.javascript.support.ScriptValueConverter;
import org.elasticsearch.search.lookup.LeafSearchLookup;
import org.elasticsearch.search.lookup.SearchLookup;
import org.mozilla.javascript.*;
import org.mozilla.javascript.Script;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
*
*/
public class JavaScriptScriptEngineService extends AbstractComponent implements ScriptEngineService {
private final AtomicLong counter = new AtomicLong();
private static WrapFactory wrapFactory = new CustomWrapFactory();
private final int optimizationLevel;
private Scriptable globalScope;
@Inject
public JavaScriptScriptEngineService(Settings settings) {
super(settings);
this.optimizationLevel = settings.getAsInt("script.javascript.optimization_level", 1);
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);
globalScope = ctx.initStandardObjects(null, true);
} finally {
Context.exit();
}
}
@Override
public void close() {
}
@Override
public void scriptRemoved(@Nullable CompiledScript compiledScript) {
// Nothing to do here
}
@Override
public String[] types() {
return new String[]{"js", "javascript"};
}
@Override
public String[] extensions() {
return new String[]{"js"};
}
@Override
public boolean sandboxed() {
return false;
}
@Override
public Object compile(String script) {
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);
ctx.setOptimizationLevel(optimizationLevel);
return ctx.compileString(script, generateScriptName(), 1, null);
} finally {
Context.exit();
}
}
@Override
public ExecutableScript executable(Object compiledScript, Map<String, Object> vars) {
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);
Scriptable scope = ctx.newObject(globalScope);
scope.setPrototype(globalScope);
scope.setParentScope(null);
for (Map.Entry<String, Object> entry : vars.entrySet()) {
ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue());
}
return new JavaScriptExecutableScript((Script) compiledScript, scope);
} finally {
Context.exit();
}
}
@Override
public SearchScript search(final Object compiledScript, final SearchLookup lookup, @Nullable final Map<String, Object> vars) {
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);
final Scriptable scope = ctx.newObject(globalScope);
scope.setPrototype(globalScope);
scope.setParentScope(null);
return new SearchScript() {
@Override
public LeafSearchScript getLeafSearchScript(LeafReaderContext context) throws IOException {
final LeafSearchLookup leafLookup = lookup.getLeafSearchLookup(context);
for (Map.Entry<String, Object> entry : leafLookup.asMap().entrySet()) {
ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue());
}
if (vars != null) {
for (Map.Entry<String, Object> entry : vars.entrySet()) {
ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue());
}
}
return new JavaScriptSearchScript((Script) compiledScript, scope, leafLookup);
}
};
} finally {
Context.exit();
}
}
@Override
public Object execute(Object compiledScript, Map<String, Object> vars) {
Context ctx = Context.enter();
ctx.setWrapFactory(wrapFactory);
try {
Script script = (Script) compiledScript;
Scriptable scope = ctx.newObject(globalScope);
scope.setPrototype(globalScope);
scope.setParentScope(null);
for (Map.Entry<String, Object> entry : vars.entrySet()) {
ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue());
}
Object ret = script.exec(ctx, scope);
return ScriptValueConverter.unwrapValue(ret);
} finally {
Context.exit();
}
}
@Override
public Object unwrap(Object value) {
return ScriptValueConverter.unwrapValue(value);
}
private String generateScriptName() {
return "Script" + counter.incrementAndGet() + ".js";
}
public static class JavaScriptExecutableScript implements ExecutableScript {
private final Script script;
private final Scriptable scope;
public JavaScriptExecutableScript(Script script, Scriptable scope) {
this.script = script;
this.scope = scope;
}
@Override
public Object run() {
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);
return ScriptValueConverter.unwrapValue(script.exec(ctx, scope));
} finally {
Context.exit();
}
}
@Override
public void setNextVar(String name, Object value) {
ScriptableObject.putProperty(scope, name, value);
}
@Override
public Object unwrap(Object value) {
return ScriptValueConverter.unwrapValue(value);
}
}
public static class JavaScriptSearchScript implements LeafSearchScript {
private final Script script;
private final Scriptable scope;
private final LeafSearchLookup lookup;
public JavaScriptSearchScript(Script script, Scriptable scope, LeafSearchLookup lookup) {
this.script = script;
this.scope = scope;
this.lookup = lookup;
}
@Override
public void setScorer(Scorer scorer) {
Context ctx = Context.enter();
try {
ScriptableObject.putProperty(scope, "_score", wrapFactory.wrapAsJavaObject(ctx, scope, new ScoreAccessor(scorer), ScoreAccessor.class));
} finally {
Context.exit();
}
}
@Override
public void setDocument(int doc) {
lookup.setDocument(doc);
}
@Override
public void setNextVar(String name, Object value) {
ScriptableObject.putProperty(scope, name, value);
}
@Override
public void setSource(Map<String, Object> source) {
lookup.source().setSource(source);
}
@Override
public Object run() {
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);
return ScriptValueConverter.unwrapValue(script.exec(ctx, scope));
} finally {
Context.exit();
}
}
@Override
public float runAsFloat() {
return ((Number) run()).floatValue();
}
@Override
public long runAsLong() {
return ((Number) run()).longValue();
}
@Override
public double runAsDouble() {
return ((Number) run()).doubleValue();
}
@Override
public Object unwrap(Object value) {
return ScriptValueConverter.unwrapValue(value);
}
}
/**
* Wrap Factory for Rhino Script Engine
*/
public static class CustomWrapFactory extends WrapFactory {
public CustomWrapFactory() {
setJavaPrimitiveWrap(false); // RingoJS does that..., claims its annoying...
}
public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class staticType) {
if (javaObject instanceof Map) {
return NativeMap.wrap(scope, (Map) javaObject);
}
if (javaObject instanceof List) {
return NativeList.wrap(scope, (List) javaObject, staticType);
}
return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
}
}
}

View File

@ -0,0 +1,149 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
import java.util.Arrays;
import java.util.List;
/**
*
*/
public class NativeList extends NativeJavaObject implements Scriptable, Wrapper {
private static final long serialVersionUID = 3664761893203964569L;
private static final String LENGTH_PROPERTY = "length";
private final List<Object> list;
public static NativeList wrap(Scriptable scope, List<Object> list, Class<?> staticType) {
return new NativeList(scope, list, staticType);
}
private NativeList(Scriptable scope, List<Object> list, Class<?> staticType) {
super(scope, list, staticType);
this.list = list;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Wrapper#unwrap()
*/
public Object unwrap() {
return list;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getClassName()
*/
public String getClassName() {
return "NativeList";
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public Object get(String name, Scriptable start) {
if (LENGTH_PROPERTY.equals(name)) {
return list.size();
} else {
return super.get(name, start);
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable)
*/
public Object get(int index, Scriptable start) {
if (has(index, start) == false) {
return Undefined.instance;
}
return list.get(index);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public boolean has(String name, Scriptable start) {
return super.has(name, start) || LENGTH_PROPERTY.equals(name);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable)
*/
public boolean has(int index, Scriptable start) {
return index >= 0 && index < list.size();
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
public void put(int index, Scriptable start, Object value) {
if (index == list.size()) {
list.add(value);
} else {
list.set(index, value);
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(int)
*/
public void delete(int index) {
list.remove(index);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getIds()
*/
public Object[] getIds() {
final Object[] javaObjectIds = super.getIds();
final int size = list.size();
final Object[] ids = Arrays.copyOf(javaObjectIds, javaObjectIds.length + size);
for (int i = 0; i < size; ++i) {
ids[javaObjectIds.length + i] = i;
}
return ids;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable)
*/
public boolean hasInstance(Scriptable value) {
if (!(value instanceof Wrapper))
return false;
Object instance = ((Wrapper) value).unwrap();
return List.class.isInstance(instance);
}
}

View File

@ -0,0 +1,223 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Wrapper;
import java.util.Iterator;
import java.util.Map;
/**
* Wrapper for exposing maps in Rhino scripts.
*
*
*/
public class NativeMap implements Scriptable, Wrapper {
private static final long serialVersionUID = 3664761893203964569L;
private Map<Object, Object> map;
private Scriptable parentScope;
private Scriptable prototype;
/**
* Construct
*
* @param scope
* @param map
* @return native map
*/
public static NativeMap wrap(Scriptable scope, Map<Object, Object> map) {
return new NativeMap(scope, map);
}
/**
* Construct
*
* @param scope
* @param map
*/
private NativeMap(Scriptable scope, Map<Object, Object> map) {
this.parentScope = scope;
this.map = map;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Wrapper#unwrap()
*/
public Object unwrap() {
return map;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getClassName()
*/
public String getClassName() {
return "NativeMap";
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public Object get(String name, Scriptable start) {
// get the property from the underlying QName map
if ("length".equals(name)) {
return map.size();
} else {
return map.get(name);
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable)
*/
public Object get(int index, Scriptable start) {
Object value = null;
int i = 0;
Iterator itrValues = map.values().iterator();
while (i++ <= index && itrValues.hasNext()) {
value = itrValues.next();
}
return value;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public boolean has(String name, Scriptable start) {
// locate the property in the underlying map
return map.containsKey(name);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable)
*/
public boolean has(int index, Scriptable start) {
return (index >= 0 && map.values().size() > index);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public void put(String name, Scriptable start, Object value) {
map.put(name, value);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
public void put(int index, Scriptable start, Object value) {
// TODO: implement?
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(java.lang.String)
*/
public void delete(String name) {
map.remove(name);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(int)
*/
public void delete(int index) {
int i = 0;
Iterator itrKeys = map.keySet().iterator();
while (i <= index && itrKeys.hasNext()) {
Object key = itrKeys.next();
if (i == index) {
map.remove(key);
break;
}
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getPrototype()
*/
public Scriptable getPrototype() {
return this.prototype;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable)
*/
public void setPrototype(Scriptable prototype) {
this.prototype = prototype;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getParentScope()
*/
public Scriptable getParentScope() {
return this.parentScope;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable)
*/
public void setParentScope(Scriptable parent) {
this.parentScope = parent;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getIds()
*/
public Object[] getIds() {
return map.keySet().toArray();
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class)
*/
public Object getDefaultValue(Class hint) {
return null;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable)
*/
public boolean hasInstance(Scriptable value) {
if (!(value instanceof Wrapper))
return false;
Object instance = ((Wrapper) value).unwrap();
return Map.class.isInstance(instance);
}
}

View File

@ -0,0 +1,183 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.*;
import java.util.*;
/**
* Value Converter to marshal objects between Java and Javascript.
*
*
*/
public final class ScriptValueConverter {
private static final String TYPE_DATE = "Date";
/**
* Private constructor - methods are static
*/
private ScriptValueConverter() {
}
/**
* Convert an object from a script wrapper value to a serializable value valid outside
* of the Rhino script processor context.
* <p/>
* This includes converting JavaScript Array objects to Lists of valid objects.
*
* @param value Value to convert from script wrapper object to external object value.
* @return unwrapped and converted value.
*/
public static Object unwrapValue(Object value) {
if (value == null) {
return null;
} else if (value instanceof Wrapper) {
// unwrap a Java object from a JavaScript wrapper
// recursively call this method to convert the unwrapped value
value = unwrapValue(((Wrapper) value).unwrap());
} else if (value instanceof IdScriptableObject) {
// check for special case Native object wrappers
String className = ((IdScriptableObject) value).getClassName();
// check for special case of the String object
if ("String".equals(className)) {
value = Context.jsToJava(value, String.class);
}
// check for special case of a Date object
else if ("Date".equals(className)) {
value = Context.jsToJava(value, Date.class);
} else {
// a scriptable object will probably indicate a multi-value property set
// set using a JavaScript associative Array object
Scriptable values = (Scriptable) value;
Object[] propIds = values.getIds();
// is it a JavaScript associative Array object using Integer indexes?
if (values instanceof NativeArray && isArray(propIds)) {
// convert JavaScript array of values to a List of Serializable objects
List<Object> propValues = new ArrayList<Object>(propIds.length);
for (int i = 0; i < propIds.length; i++) {
// work on each key in turn
Integer propId = (Integer) propIds[i];
// we are only interested in keys that indicate a list of values
if (propId instanceof Integer) {
// get the value out for the specified key
Object val = values.get(propId, values);
// recursively call this method to convert the value
propValues.add(unwrapValue(val));
}
}
value = propValues;
} else {
// any other JavaScript object that supports properties - convert to a Map of objects
Map<String, Object> propValues = new HashMap<String, Object>(propIds.length);
for (int i = 0; i < propIds.length; i++) {
// work on each key in turn
Object propId = propIds[i];
// we are only interested in keys that indicate a list of values
if (propId instanceof String) {
// get the value out for the specified key
Object val = values.get((String) propId, values);
// recursively call this method to convert the value
propValues.put((String) propId, unwrapValue(val));
}
}
value = propValues;
}
}
} else if (value instanceof Object[]) {
// convert back a list Object Java values
Object[] array = (Object[]) value;
ArrayList<Object> list = new ArrayList<Object>(array.length);
for (int i = 0; i < array.length; i++) {
list.add(unwrapValue(array[i]));
}
value = list;
} else if (value instanceof Map) {
// ensure each value in the Map is unwrapped (which may have been an unwrapped NativeMap!)
Map<Object, Object> map = (Map<Object, Object>) value;
Map<Object, Object> copyMap = new HashMap<Object, Object>(map.size());
for (Object key : map.keySet()) {
copyMap.put(key, unwrapValue(map.get(key)));
}
value = copyMap;
}
return value;
}
/**
* Convert an object from any repository serialized value to a valid script object.
* This includes converting Collection multi-value properties into JavaScript Array objects.
*
* @param scope Scripting scope
* @param value Property value
* @return Value safe for scripting usage
*/
public static Object wrapValue(Scriptable scope, Object value) {
// perform conversions from Java objects to JavaScript scriptable instances
if (value == null) {
return null;
} else if (value instanceof Date) {
// convert Date to JavaScript native Date object
// call the "Date" constructor on the root scope object - passing in the millisecond
// value from the Java date - this will construct a JavaScript Date with the same value
Date date = (Date) value;
value = ScriptRuntime.newObject(
Context.getCurrentContext(), scope, TYPE_DATE, new Object[]{date.getTime()});
} else if (value instanceof Collection) {
// recursively convert each value in the collection
Collection<Object> collection = (Collection<Object>) value;
Object[] array = new Object[collection.size()];
int index = 0;
for (Object obj : collection) {
array[index++] = wrapValue(scope, obj);
}
// convert array to a native JavaScript Array
value = Context.getCurrentContext().newArray(scope, array);
} else if (value instanceof Map) {
value = NativeMap.wrap(scope, (Map) value);
}
// simple numbers, strings and booleans are wrapped automatically by Rhino
return value;
}
/**
* Look at the id's of a native array and try to determine whether it's actually an Array or a Hashmap
*
* @param ids id's of the native array
* @return boolean true if it's an array, false otherwise (ie it's a map)
*/
private static boolean isArray(final Object[] ids) {
boolean result = true;
for (int i = 0; i < ids.length; i++) {
if (ids[i] instanceof Integer == false) {
result = false;
break;
}
}
return result;
}
}

View File

@ -0,0 +1,188 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.Scriptable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Implementation of a Scriptable Map. This is the best choice for maps that want to represent
* JavaScript associative arrays - allowing access via key and integer index. It maintains and
* respects insertion order of the elements and allows either string or integer keys.
*
*
*/
public class ScriptableLinkedHashMap<K, V> extends LinkedHashMap<K, V> implements ScriptableMap<K, V> {
private static final long serialVersionUID = 3774167893214964123L;
private Scriptable parentScope;
private Scriptable prototype;
public ScriptableLinkedHashMap() {
}
public ScriptableLinkedHashMap(int initialCapacity) {
super(initialCapacity);
}
public ScriptableLinkedHashMap(Map<K, V> source) {
super(source);
}
/**
* @see org.mozilla.javascript.Scriptable#getClassName()
*/
public String getClassName() {
return "ScriptableMap";
}
/**
* @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public Object get(String name, Scriptable start) {
// get the property from the underlying QName map
if ("length".equals(name)) {
return this.size();
} else {
return get(name);
}
}
/**
* @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable)
*/
public Object get(int index, Scriptable start) {
Object value = null;
int i = 0;
Iterator itrValues = this.values().iterator();
while (i++ <= index && itrValues.hasNext()) {
value = itrValues.next();
}
return value;
}
/**
* @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public boolean has(String name, Scriptable start) {
// locate the property in the underlying map
return containsKey(name);
}
/**
* @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable)
*/
public boolean has(int index, Scriptable start) {
return (index >= 0 && this.values().size() > index);
}
/**
* @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public void put(String name, Scriptable start, Object value) {
// add the property to the underlying QName map
put((K) name, (V) value);
}
/**
* @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
public void put(int index, Scriptable start, Object value) {
// TODO: implement?
}
/**
* @see org.mozilla.javascript.Scriptable#delete(java.lang.String)
*/
public void delete(String name) {
// remove the property from the underlying QName map
remove(name);
}
/**
* @see org.mozilla.javascript.Scriptable#delete(int)
*/
public void delete(int index) {
int i = 0;
Iterator itrKeys = this.keySet().iterator();
while (i <= index && itrKeys.hasNext()) {
Object key = itrKeys.next();
if (i == index) {
remove(key);
break;
}
}
}
/**
* @see org.mozilla.javascript.Scriptable#getPrototype()
*/
public Scriptable getPrototype() {
return this.prototype;
}
/**
* @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable)
*/
public void setPrototype(Scriptable prototype) {
this.prototype = prototype;
}
/**
* @see org.mozilla.javascript.Scriptable#getParentScope()
*/
public Scriptable getParentScope() {
return this.parentScope;
}
/**
* @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable)
*/
public void setParentScope(Scriptable parent) {
this.parentScope = parent;
}
/**
* @see org.mozilla.javascript.Scriptable#getIds()
*/
public Object[] getIds() {
return keySet().toArray();
}
/**
* @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class)
*/
public Object getDefaultValue(Class hint) {
return null;
}
/**
* @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable)
*/
public boolean hasInstance(Scriptable instance) {
return instance instanceof ScriptableLinkedHashMap;
}
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.Scriptable;
import java.util.Map;
/**
* Contract to be implemented by classes providing Map like collections to JavaScript.
*
*
*/
public interface ScriptableMap<K, V> extends Scriptable, Map<K, V> {
}

View File

@ -0,0 +1,342 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript.support;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Wrapper;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Implementation of a Scriptable Map. This is the best choice where you want values to be
* persisted directly to an underlying map supplied on construction. The class automatically
* wraps/unwraps JS objects as they enter/leave the underlying map via the Scriptable interface
* methods - objects are untouched if accessed via the usual Map interface methods.
* <p/>
* <p>Access should be by string key only - not integer index - unless you are sure the wrapped
* map will maintain insertion order of the elements.
*
*
*/
public class ScriptableWrappedMap implements ScriptableMap, Wrapper {
private Map map;
private Scriptable parentScope;
private Scriptable prototype;
/**
* Construction
*
* @param scope
* @param map
* @return scriptable wrapped map
*/
public static ScriptableWrappedMap wrap(Scriptable scope, Map<Object, Object> map) {
return new ScriptableWrappedMap(scope, map);
}
/**
* Construct
*
* @param map
*/
public ScriptableWrappedMap(Map map) {
this.map = map;
}
/**
* Construct
*
* @param scope
* @param map
*/
public ScriptableWrappedMap(Scriptable scope, Map map) {
this.parentScope = scope;
this.map = map;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Wrapper#unwrap()
*/
public Object unwrap() {
return map;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getClassName()
*/
public String getClassName() {
return "ScriptableWrappedMap";
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public Object get(String name, Scriptable start) {
// get the property from the underlying QName map
if ("length".equals(name)) {
return map.size();
} else {
return ScriptValueConverter.wrapValue(this.parentScope != null ? this.parentScope : start, map.get(name));
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable)
*/
public Object get(int index, Scriptable start) {
Object value = null;
int i = 0;
Iterator itrValues = map.values().iterator();
while (i++ <= index && itrValues.hasNext()) {
value = itrValues.next();
}
return ScriptValueConverter.wrapValue(this.parentScope != null ? this.parentScope : start, value);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable)
*/
public boolean has(String name, Scriptable start) {
// locate the property in the underlying map
return map.containsKey(name);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable)
*/
public boolean has(int index, Scriptable start) {
return (index >= 0 && map.values().size() > index);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public void put(String name, Scriptable start, Object value) {
map.put(name, ScriptValueConverter.unwrapValue(value));
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object)
*/
public void put(int index, Scriptable start, Object value) {
// TODO: implement?
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(java.lang.String)
*/
public void delete(String name) {
map.remove(name);
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#delete(int)
*/
public void delete(int index) {
int i = 0;
Iterator itrKeys = map.keySet().iterator();
while (i <= index && itrKeys.hasNext()) {
Object key = itrKeys.next();
if (i == index) {
map.remove(key);
break;
}
}
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getPrototype()
*/
public Scriptable getPrototype() {
return this.prototype;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable)
*/
public void setPrototype(Scriptable prototype) {
this.prototype = prototype;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getParentScope()
*/
public Scriptable getParentScope() {
return this.parentScope;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable)
*/
public void setParentScope(Scriptable parent) {
this.parentScope = parent;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getIds()
*/
public Object[] getIds() {
return map.keySet().toArray();
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class)
*/
public Object getDefaultValue(Class hint) {
return null;
}
/* (non-Javadoc)
* @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable)
*/
public boolean hasInstance(Scriptable value) {
if (!(value instanceof Wrapper))
return false;
Object instance = ((Wrapper) value).unwrap();
return Map.class.isInstance(instance);
}
/* (non-Javadoc)
* @see java.util.Map#clear()
*/
public void clear() {
this.map.clear();
}
/* (non-Javadoc)
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key) {
return this.map.containsKey(key);
}
/* (non-Javadoc)
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(Object value) {
return this.map.containsValue(value);
}
/* (non-Javadoc)
* @see java.util.Map#entrySet()
*/
public Set entrySet() {
return this.map.entrySet();
}
/* (non-Javadoc)
* @see java.util.Map#get(java.lang.Object)
*/
public Object get(Object key) {
return this.map.get(key);
}
/* (non-Javadoc)
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty() {
return (this.map.size() == 0);
}
/* (non-Javadoc)
* @see java.util.Map#keySet()
*/
public Set keySet() {
return this.map.keySet();
}
/* (non-Javadoc)
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public Object put(Object key, Object value) {
return this.map.put(key, value);
}
/* (non-Javadoc)
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(Map t) {
this.map.putAll(t);
}
/* (non-Javadoc)
* @see java.util.Map#remove(java.lang.Object)
*/
public Object remove(Object key) {
return this.map.remove(key);
}
/* (non-Javadoc)
* @see java.util.Map#size()
*/
public int size() {
return this.map.size();
}
/* (non-Javadoc)
* @see java.util.Map#values()
*/
public Collection values() {
return this.map.values();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return (this.map != null ? this.map.toString() : super.toString());
}
}

View File

@ -0,0 +1,2 @@
plugin=org.elasticsearch.plugin.javascript.JavaScriptPlugin
version=${project.version}

View File

@ -0,0 +1,174 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
/**
*
*/
public class JavaScriptScriptEngineTests extends ElasticsearchTestCase {
private JavaScriptScriptEngineService se;
@Before
public void setup() {
se = new JavaScriptScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
}
@After
public void close() {
se.close();
}
@Test
public void testSimpleEquation() {
Map<String, Object> vars = new HashMap<String, Object>();
Object o = se.execute(se.compile("1 + 2"), vars);
assertThat(((Number) o).intValue(), equalTo(3));
}
@Test
public void testMapAccess() {
Map<String, Object> vars = new HashMap<String, Object>();
Map<String, Object> obj2 = MapBuilder.<String, Object>newMapBuilder().put("prop2", "value2").map();
Map<String, Object> obj1 = MapBuilder.<String, Object>newMapBuilder().put("prop1", "value1").put("obj2", obj2).put("l", Arrays.asList("2", "1")).map();
vars.put("obj1", obj1);
Object o = se.execute(se.compile("obj1"), vars);
assertThat(o, instanceOf(Map.class));
obj1 = (Map<String, Object>) o;
assertThat((String) obj1.get("prop1"), equalTo("value1"));
assertThat((String) ((Map<String, Object>) obj1.get("obj2")).get("prop2"), equalTo("value2"));
o = se.execute(se.compile("obj1.l[0]"), vars);
assertThat(((String) o), equalTo("2"));
}
@Test
public void testJavaScriptObjectToMap() {
Map<String, Object> vars = new HashMap<String, Object>();
Object o = se.execute(se.compile("var obj1 = {}; obj1.prop1 = 'value1'; obj1.obj2 = {}; obj1.obj2.prop2 = 'value2'; obj1"), vars);
Map obj1 = (Map) o;
assertThat((String) obj1.get("prop1"), equalTo("value1"));
assertThat((String) ((Map<String, Object>) obj1.get("obj2")).get("prop2"), equalTo("value2"));
}
@Test
public void testJavaScriptObjectMapInter() {
Map<String, Object> vars = new HashMap<String, Object>();
Map<String, Object> ctx = new HashMap<String, Object>();
Map<String, Object> obj1 = new HashMap<String, Object>();
obj1.put("prop1", "value1");
ctx.put("obj1", obj1);
vars.put("ctx", ctx);
se.execute(se.compile("ctx.obj2 = {}; ctx.obj2.prop2 = 'value2'; ctx.obj1.prop1 = 'uvalue1'"), vars);
ctx = (Map<String, Object>) se.unwrap(vars.get("ctx"));
assertThat(ctx.containsKey("obj1"), equalTo(true));
assertThat((String) ((Map<String, Object>) ctx.get("obj1")).get("prop1"), equalTo("uvalue1"));
assertThat(ctx.containsKey("obj2"), equalTo(true));
assertThat((String) ((Map<String, Object>) ctx.get("obj2")).get("prop2"), equalTo("value2"));
}
@Test
public void testJavaScriptInnerArrayCreation() {
Map<String, Object> ctx = new HashMap<String, Object>();
Map<String, Object> doc = new HashMap<String, Object>();
ctx.put("doc", doc);
Object complied = se.compile("ctx.doc.field1 = ['value1', 'value2']");
ExecutableScript script = se.executable(complied, new HashMap<String, Object>());
script.setNextVar("ctx", ctx);
script.run();
Map<String, Object> unwrap = (Map<String, Object>) script.unwrap(ctx);
assertThat(((Map) unwrap.get("doc")).get("field1"), instanceOf(List.class));
}
@Test
public void testAccessListInScript() {
Map<String, Object> vars = new HashMap<String, Object>();
Map<String, Object> obj2 = MapBuilder.<String, Object>newMapBuilder().put("prop2", "value2").map();
Map<String, Object> obj1 = MapBuilder.<String, Object>newMapBuilder().put("prop1", "value1").put("obj2", obj2).map();
vars.put("l", Arrays.asList("1", "2", "3", obj1));
Object o = se.execute(se.compile("l.length"), vars);
assertThat(((Number) o).intValue(), equalTo(4));
o = se.execute(se.compile("l[0]"), vars);
assertThat(((String) o), equalTo("1"));
o = se.execute(se.compile("l[3]"), vars);
obj1 = (Map<String, Object>) o;
assertThat((String) obj1.get("prop1"), equalTo("value1"));
assertThat((String) ((Map<String, Object>) obj1.get("obj2")).get("prop2"), equalTo("value2"));
o = se.execute(se.compile("l[3].prop1"), vars);
assertThat(((String) o), equalTo("value1"));
}
@Test
public void testChangingVarsCrossExecution1() {
Map<String, Object> vars = new HashMap<String, Object>();
Map<String, Object> ctx = new HashMap<String, Object>();
vars.put("ctx", ctx);
Object compiledScript = se.compile("ctx.value");
ExecutableScript script = se.executable(compiledScript, vars);
ctx.put("value", 1);
Object o = script.run();
assertThat(((Number) o).intValue(), equalTo(1));
ctx.put("value", 2);
o = script.run();
assertThat(((Number) o).intValue(), equalTo(2));
}
@Test
public void testChangingVarsCrossExecution2() {
Map<String, Object> vars = new HashMap<String, Object>();
Object compiledScript = se.compile("value");
ExecutableScript script = se.executable(compiledScript, vars);
script.setNextVar("value", 1);
Object o = script.run();
assertThat(((Number) o).intValue(), equalTo(1));
script.setNextVar("value", 2);
o = script.run();
assertThat(((Number) o).intValue(), equalTo(2));
}
}

View File

@ -0,0 +1,169 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.Matchers.equalTo;
/**
*
*/
public class JavaScriptScriptMultiThreadedTest extends ElasticsearchTestCase {
@Test
public void testExecutableNoRuntimeParams() throws Exception {
final JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
final Object compiled = se.compile("x + y");
final AtomicBoolean failed = new AtomicBoolean();
Thread[] threads = new Thread[50];
final CountDownLatch latch = new CountDownLatch(threads.length);
final CyclicBarrier barrier = new CyclicBarrier(threads.length + 1);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
barrier.await();
long x = ThreadLocalRandom.current().nextInt();
long y = ThreadLocalRandom.current().nextInt();
long addition = x + y;
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("x", x);
vars.put("y", y);
ExecutableScript script = se.executable(compiled, vars);
for (int i = 0; i < 100000; i++) {
long result = ((Number) script.run()).longValue();
assertThat(result, equalTo(addition));
}
} catch (Throwable t) {
failed.set(true);
logger.error("failed", t);
} finally {
latch.countDown();
}
}
});
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
barrier.await();
latch.await();
assertThat(failed.get(), equalTo(false));
}
@Test
public void testExecutableWithRuntimeParams() throws Exception {
final JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
final Object compiled = se.compile("x + y");
final AtomicBoolean failed = new AtomicBoolean();
Thread[] threads = new Thread[50];
final CountDownLatch latch = new CountDownLatch(threads.length);
final CyclicBarrier barrier = new CyclicBarrier(threads.length + 1);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
barrier.await();
long x = ThreadLocalRandom.current().nextInt();
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("x", x);
ExecutableScript script = se.executable(compiled, vars);
for (int i = 0; i < 100000; i++) {
long y = ThreadLocalRandom.current().nextInt();
long addition = x + y;
script.setNextVar("y", y);
long result = ((Number) script.run()).longValue();
assertThat(result, equalTo(addition));
}
} catch (Throwable t) {
failed.set(true);
logger.error("failed", t);
} finally {
latch.countDown();
}
}
});
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
barrier.await();
latch.await();
assertThat(failed.get(), equalTo(false));
}
@Test
public void testExecute() throws Exception {
final JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
final Object compiled = se.compile("x + y");
final AtomicBoolean failed = new AtomicBoolean();
Thread[] threads = new Thread[50];
final CountDownLatch latch = new CountDownLatch(threads.length);
final CyclicBarrier barrier = new CyclicBarrier(threads.length + 1);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
barrier.await();
Map<String, Object> runtimeVars = new HashMap<String, Object>();
for (int i = 0; i < 100000; i++) {
long x = ThreadLocalRandom.current().nextInt();
long y = ThreadLocalRandom.current().nextInt();
long addition = x + y;
runtimeVars.put("x", x);
runtimeVars.put("y", y);
long result = ((Number) se.execute(compiled, runtimeVars)).longValue();
assertThat(result, equalTo(addition));
}
} catch (Throwable t) {
failed.set(true);
logger.error("failed", t);
} finally {
latch.countDown();
}
}
});
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
barrier.await();
latch.await();
assertThat(failed.get(), equalTo(false));
}
}

View File

@ -0,0 +1,299 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.client.Requests.searchRequest;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.scriptFunction;
import static org.elasticsearch.search.aggregations.AggregationBuilders.terms;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
*
*/
@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE)
public class JavaScriptScriptSearchTests extends ElasticsearchIntegrationTest {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
.build();
}
@Test
public void testJavaScriptFilter() throws Exception {
createIndex("test");
index("test", "type1", "1", jsonBuilder().startObject().field("test", "value beck").field("num1", 1.0f).endObject());
flush();
index("test", "type1", "2", jsonBuilder().startObject().field("test", "value beck").field("num1", 2.0f).endObject());
flush();
index("test", "type1", "3", jsonBuilder().startObject().field("test", "value beck").field("num1", 3.0f).endObject());
refresh();
logger.info(" --> running doc['num1'].value > 1");
SearchResponse response = client().prepareSearch()
.setQuery(filteredQuery(matchAllQuery(), scriptQuery("doc['num1'].value > 1").lang("js")))
.addSort("num1", SortOrder.ASC)
.addScriptField("sNum1", "js", "doc['num1'].value", null)
.execute().actionGet();
assertThat(response.getHits().totalHits(), equalTo(2l));
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
assertThat((Double) response.getHits().getAt(0).fields().get("sNum1").values().get(0), equalTo(2.0));
assertThat(response.getHits().getAt(1).id(), equalTo("3"));
assertThat((Double) response.getHits().getAt(1).fields().get("sNum1").values().get(0), equalTo(3.0));
logger.info(" --> running doc['num1'].value > param1");
response = client().prepareSearch()
.setQuery(filteredQuery(matchAllQuery(), scriptQuery("doc['num1'].value > param1").lang("js").addParam("param1", 2)))
.addSort("num1", SortOrder.ASC)
.addScriptField("sNum1", "js", "doc['num1'].value", null)
.execute().actionGet();
assertThat(response.getHits().totalHits(), equalTo(1l));
assertThat(response.getHits().getAt(0).id(), equalTo("3"));
assertThat((Double) response.getHits().getAt(0).fields().get("sNum1").values().get(0), equalTo(3.0));
logger.info(" --> running doc['num1'].value > param1");
response = client().prepareSearch()
.setQuery(filteredQuery(matchAllQuery(), scriptQuery("doc['num1'].value > param1").lang("js").addParam("param1", -1)))
.addSort("num1", SortOrder.ASC)
.addScriptField("sNum1", "js", "doc['num1'].value", null)
.execute().actionGet();
assertThat(response.getHits().totalHits(), equalTo(3l));
assertThat(response.getHits().getAt(0).id(), equalTo("1"));
assertThat((Double) response.getHits().getAt(0).fields().get("sNum1").values().get(0), equalTo(1.0));
assertThat(response.getHits().getAt(1).id(), equalTo("2"));
assertThat((Double) response.getHits().getAt(1).fields().get("sNum1").values().get(0), equalTo(2.0));
assertThat(response.getHits().getAt(2).id(), equalTo("3"));
assertThat((Double) response.getHits().getAt(2).fields().get("sNum1").values().get(0), equalTo(3.0));
}
@Test
public void testScriptFieldUsingSource() throws Exception {
createIndex("test");
index("test", "type1", "1",
jsonBuilder().startObject()
.startObject("obj1").field("test", "something").endObject()
.startObject("obj2").startArray("arr2").value("arr_value1").value("arr_value2").endArray().endObject()
.endObject());
refresh();
SearchResponse response = client().prepareSearch()
.setQuery(matchAllQuery())
.addScriptField("s_obj1", "js", "_source.obj1", null)
.addScriptField("s_obj1_test", "js", "_source.obj1.test", null)
.addScriptField("s_obj2", "js", "_source.obj2", null)
.addScriptField("s_obj2_arr2", "js", "_source.obj2.arr2", null)
.execute().actionGet();
Map<String, Object> sObj1 = (Map<String, Object>) response.getHits().getAt(0).field("s_obj1").value();
assertThat(sObj1.get("test").toString(), equalTo("something"));
assertThat(response.getHits().getAt(0).field("s_obj1_test").value().toString(), equalTo("something"));
Map<String, Object> sObj2 = (Map<String, Object>) response.getHits().getAt(0).field("s_obj2").value();
List sObj2Arr2 = (List) sObj2.get("arr2");
assertThat(sObj2Arr2.size(), equalTo(2));
assertThat(sObj2Arr2.get(0).toString(), equalTo("arr_value1"));
assertThat(sObj2Arr2.get(1).toString(), equalTo("arr_value2"));
sObj2Arr2 = (List) response.getHits().getAt(0).field("s_obj2_arr2").values();
assertThat(sObj2Arr2.size(), equalTo(2));
assertThat(sObj2Arr2.get(0).toString(), equalTo("arr_value1"));
assertThat(sObj2Arr2.get(1).toString(), equalTo("arr_value2"));
}
@Test
public void testCustomScriptBoost() throws Exception {
createIndex("test");
index("test", "type1", "1", jsonBuilder().startObject().field("test", "value beck").field("num1", 1.0f).endObject());
index("test", "type1", "2", jsonBuilder().startObject().field("test", "value beck").field("num1", 2.0f).endObject());
refresh();
logger.info("--- QUERY_THEN_FETCH");
logger.info(" --> running doc['num1'].value");
SearchResponse response = client().search(searchRequest()
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource().explain(true).query(functionScoreQuery(termQuery("test", "value"))
.add(ScoreFunctionBuilders.scriptFunction("doc['num1'].value").lang("js"))))
).actionGet();
assertThat("Failures " + Arrays.toString(response.getShardFailures()), response.getShardFailures().length, equalTo(0));
assertThat(response.getHits().totalHits(), equalTo(2l));
logger.info(" --> Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation());
logger.info(" --> Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation());
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
assertThat(response.getHits().getAt(1).id(), equalTo("1"));
logger.info(" --> running -doc['num1'].value");
response = client().search(searchRequest()
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource().explain(true).query(functionScoreQuery(termQuery("test", "value"))
.add(ScoreFunctionBuilders.scriptFunction("-doc['num1'].value").lang("js"))))
).actionGet();
assertThat("Failures " + Arrays.toString(response.getShardFailures()), response.getShardFailures().length, equalTo(0));
assertThat(response.getHits().totalHits(), equalTo(2l));
logger.info(" --> Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation());
logger.info(" --> Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation());
assertThat(response.getHits().getAt(0).id(), equalTo("1"));
assertThat(response.getHits().getAt(1).id(), equalTo("2"));
logger.info(" --> running pow(doc['num1'].value, 2)");
response = client().search(searchRequest()
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource().explain(true).query(functionScoreQuery(termQuery("test", "value"))
.add(ScoreFunctionBuilders.scriptFunction("Math.pow(doc['num1'].value, 2)").lang("js"))))
).actionGet();
assertThat("Failures " + Arrays.toString(response.getShardFailures()), response.getShardFailures().length, equalTo(0));
assertThat(response.getHits().totalHits(), equalTo(2l));
logger.info(" --> Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation());
logger.info(" --> Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation());
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
assertThat(response.getHits().getAt(1).id(), equalTo("1"));
logger.info(" --> running max(doc['num1'].value, 1)");
response = client().search(searchRequest()
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource().explain(true).query(functionScoreQuery(termQuery("test", "value"))
.add(ScoreFunctionBuilders.scriptFunction("Math.max(doc['num1'].value, 1)").lang("js"))))
).actionGet();
assertThat("Failures " + Arrays.toString(response.getShardFailures()), response.getShardFailures().length, equalTo(0));
assertThat(response.getHits().totalHits(), equalTo(2l));
logger.info(" --> Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation());
logger.info(" --> Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation());
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
assertThat(response.getHits().getAt(1).id(), equalTo("1"));
logger.info(" --> running doc['num1'].value * _score");
response = client().search(searchRequest()
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource().explain(true).query(functionScoreQuery(termQuery("test", "value"))
.add(ScoreFunctionBuilders.scriptFunction("doc['num1'].value * _score").lang("js"))))
).actionGet();
assertThat("Failures " + Arrays.toString(response.getShardFailures()), response.getShardFailures().length, equalTo(0));
assertThat(response.getHits().totalHits(), equalTo(2l));
logger.info(" --> Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation());
logger.info(" --> Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation());
assertThat(response.getHits().getAt(0).id(), equalTo("2"));
assertThat(response.getHits().getAt(1).id(), equalTo("1"));
logger.info(" --> running param1 * param2 * _score");
response = client().search(searchRequest()
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource().explain(true).query(functionScoreQuery(termQuery("test", "value"))
.add(ScoreFunctionBuilders.scriptFunction("param1 * param2 * _score").param("param1", 2).param("param2", 2).lang("js"))))
).actionGet();
assertThat("Failures " + Arrays.toString(response.getShardFailures()), response.getShardFailures().length, equalTo(0));
assertThat(response.getHits().totalHits(), equalTo(2l));
logger.info(" --> Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation());
logger.info(" --> Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation());
}
@Test
public void testScriptScoresNested() throws IOException {
createIndex("index");
ensureYellow();
index("index", "testtype", "1", jsonBuilder().startObject().field("dummy_field", 1).endObject());
refresh();
SearchResponse response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery(
functionScoreQuery(
functionScoreQuery().add(scriptFunction("1").lang("js")))
.add(scriptFunction("_score.doubleValue()").lang("js")))
.add(scriptFunction("_score.doubleValue()").lang("js")
)
)
)
).actionGet();
assertSearchResponse(response);
assertThat(response.getHits().getAt(0).score(), equalTo(1.0f));
}
@Test
public void testScriptScoresWithAgg() throws IOException {
createIndex("index");
ensureYellow();
index("index", "testtype", "1", jsonBuilder().startObject().field("dummy_field", 1).endObject());
refresh();
SearchResponse response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery()
.add(scriptFunction("_score.doubleValue()").lang("js")
)
).aggregation(terms("score_agg").script("_score.doubleValue()").lang("js"))
)
).actionGet();
assertSearchResponse(response);
assertThat(response.getHits().getAt(0).score(), equalTo(1.0f));
assertThat(((Terms) response.getAggregations().asMap().get("score_agg")).getBuckets().get(0).getKeyAsNumber().floatValue(), is(1f));
assertThat(((Terms) response.getAggregations().asMap().get("score_agg")).getBuckets().get(0).getDocCount(), is(1l));
}
@Test
public void testUseListLengthInScripts() throws Exception {
createIndex("index");
index("index", "testtype", "1", jsonBuilder().startObject().field("f", 42).endObject());
ensureSearchable("index");
refresh();
SearchResponse response = client().prepareSearch().addScriptField("foobar", "js", "doc['f'].values.length", null).get();
assertSearchResponse(response);
assertHitCount(response, 1);
assertThat((Integer) response.getHits().getAt(0).getFields().get("foobar").value(), equalTo(1));
}
}

View File

@ -0,0 +1,71 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script.javascript;
import org.elasticsearch.common.StopWatch;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.script.ExecutableScript;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class SimpleBench {
public static void main(String[] args) {
JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
Object compiled = se.compile("x + y");
Map<String, Object> vars = new HashMap<String, Object>();
// warm up
for (int i = 0; i < 1000; i++) {
vars.put("x", i);
vars.put("y", i + 1);
se.execute(compiled, vars);
}
final long ITER = 100000;
StopWatch stopWatch = new StopWatch().start();
for (long i = 0; i < ITER; i++) {
se.execute(compiled, vars);
}
System.out.println("Execute Took: " + stopWatch.stop().lastTaskTime());
stopWatch = new StopWatch().start();
ExecutableScript executableScript = se.executable(compiled, vars);
for (long i = 0; i < ITER; i++) {
executableScript.run();
}
System.out.println("Executable Took: " + stopWatch.stop().lastTaskTime());
stopWatch = new StopWatch().start();
executableScript = se.executable(compiled, vars);
for (long i = 0; i < ITER; i++) {
for (Map.Entry<String, Object> entry : vars.entrySet()) {
executableScript.setNextVar(entry.getKey(), entry.getValue());
}
executableScript.run();
}
System.out.println("Executable (vars) Took: " + stopWatch.stop().lastTaskTime());
}
}