From 5efc712d5c774a8d0bfdb4a00ace1e8cc94c18df Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Mon, 5 Dec 2011 17:23:33 +0200 Subject: [PATCH] first commit --- .gitignore | 7 + README.md | 15 + pom.xml | 122 +++++++ src/main/assemblies/plugin.xml | 26 ++ .../plugin/javascript/JavaScriptPlugin.java | 48 +++ .../JavaScriptScriptEngineService.java | 298 +++++++++++++++ .../script/javascript/support/NativeList.java | 207 +++++++++++ .../script/javascript/support/NativeMap.java | 223 ++++++++++++ .../support/ScriptValueConverter.java | 183 ++++++++++ .../support/ScriptableLinkedHashMap.java | 188 ++++++++++ .../javascript/support/ScriptableMap.java | 32 ++ .../support/ScriptableWrappedMap.java | 342 ++++++++++++++++++ src/main/resources/es-plugin.properties | 1 + .../JavaScriptScriptEngineTests.java | 174 +++++++++ .../JavaScriptScriptMultiThreadedTest.java | 174 +++++++++ .../JavaScriptScriptSearchTests.java | 267 ++++++++++++++ .../script/javascript/SimpleBench.java | 71 ++++ src/test/resources/log4j.properties | 5 + 18 files changed, 2383 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/assemblies/plugin.xml create mode 100644 src/main/java/org/elasticsearch/plugin/javascript/JavaScriptPlugin.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/support/NativeList.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/support/NativeMap.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/support/ScriptValueConverter.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/support/ScriptableLinkedHashMap.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/support/ScriptableMap.java create mode 100644 src/main/java/org/elasticsearch/script/javascript/support/ScriptableWrappedMap.java create mode 100644 src/main/resources/es-plugin.properties create mode 100644 src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java create mode 100644 src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptMultiThreadedTest.java create mode 100644 src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptSearchTests.java create mode 100644 src/test/java/org/elasticsearch/script/javascript/SimpleBench.java create mode 100644 src/test/resources/log4j.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..06a1e6fedb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +*.iml diff --git a/README.md b/README.md new file mode 100644 index 00000000000..f56c629bfae --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +JavaScript lang Plugin for ElasticSearch +================================== + +The JavaScript language plugin allows to have `javascript` as the language of scripts to execute. + +In order to install the plugin, simply run: `bin/plugin -install elasticsearch/elasticsearch-lang-javascript/1.0.0`. + + --------------------------------------- + | AWS Cloud Plugin | ElasticSearch | + --------------------------------------- + | master | 0.18 -> master | + --------------------------------------- + | 1.0.0 | 0.18 -> master | + --------------------------------------- + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..87365b528d6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,122 @@ + + + elasticsearch-lang-javascript + 4.0.0 + org.elasticsearch + elasticsearch-lang-javascript + 1.0.0 + jar + JavaScript lang plugin for ElasticSearch + 2009 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:git:git@github.com:elasticsearch/elasticsearch-lang-javascript.git + scm:git:git@github.com:elasticsearch/elasticsearch-lang-javascript.git + http://github.com/elasticsearch/elasticsearch-lang-javascript + + + + org.sonatype.oss + oss-parent + 7 + + + + 0.18.5 + + + + + + + + org.elasticsearch + elasticsearch + ${elasticsearch.version} + compile + + + + org.mozilla + rhino + 1.7R3 + compile + + + + + + org.testng + testng + 6.3.1 + test + + + + org.hamcrest + hamcrest-core + 1.3.RC2 + test + + + + org.hamcrest + hamcrest-library + 1.3.RC2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.11 + + + **/*Tests.java + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + + jar + + + + + + maven-assembly-plugin + + + ${basedir}/src/main/assemblies/plugin.xml + + + + + + \ No newline at end of file diff --git a/src/main/assemblies/plugin.xml b/src/main/assemblies/plugin.xml new file mode 100644 index 00000000000..2a637316fa0 --- /dev/null +++ b/src/main/assemblies/plugin.xml @@ -0,0 +1,26 @@ + + + + + zip + + false + + + / + true + true + + org.elasticsearch:elasticsearch + + + + / + true + true + + org.mozilla:rhino + + + + \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/plugin/javascript/JavaScriptPlugin.java b/src/main/java/org/elasticsearch/plugin/javascript/JavaScriptPlugin.java new file mode 100644 index 00000000000..670194d2c4a --- /dev/null +++ b/src/main/java/org/elasticsearch/plugin/javascript/JavaScriptPlugin.java @@ -0,0 +1,48 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.common.inject.Module; +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"; + } + + @Override + public void processModule(Module module) { + if (module instanceof ScriptModule) { + ((ScriptModule) module).addScriptEngine(JavaScriptScriptEngineService.class); + } + } +} diff --git a/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java b/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java new file mode 100644 index 00000000000..d1d35d453bb --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java @@ -0,0 +1,298 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.IndexReader; +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.ExecutableScript; +import org.elasticsearch.script.ScriptEngineService; +import org.elasticsearch.script.SearchScript; +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.SearchLookup; +import org.mozilla.javascript.*; + +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 = componentSettings.getAsInt("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 String[] types() { + return new String[]{"js", "javascript"}; + } + + @Override + public String[] extensions() { + return new String[]{"js"}; + } + + @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 vars) { + Context ctx = Context.enter(); + try { + ctx.setWrapFactory(wrapFactory); + + Scriptable scope = ctx.newObject(globalScope); + scope.setPrototype(globalScope); + scope.setParentScope(null); + for (Map.Entry entry : vars.entrySet()) { + ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue()); + } + + return new JavaScriptExecutableScript((Script) compiledScript, scope); + } finally { + Context.exit(); + } + } + + @Override + public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map vars) { + Context ctx = Context.enter(); + try { + ctx.setWrapFactory(wrapFactory); + + Scriptable scope = ctx.newObject(globalScope); + scope.setPrototype(globalScope); + scope.setParentScope(null); + + for (Map.Entry entry : lookup.asMap().entrySet()) { + ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue()); + } + + if (vars != null) { + for (Map.Entry entry : vars.entrySet()) { + ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue()); + } + } + + return new JavaScriptSearchScript((Script) compiledScript, scope, lookup); + } finally { + Context.exit(); + } + } + + @Override + public Object execute(Object compiledScript, Map 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 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 SearchScript { + + private final Script script; + + private final Scriptable scope; + + private final SearchLookup lookup; + + public JavaScriptSearchScript(Script script, Scriptable scope, SearchLookup lookup) { + this.script = script; + this.scope = scope; + this.lookup = lookup; + } + + @Override + public void setScorer(Scorer scorer) { + lookup.setScorer(scorer); + } + + @Override + public void setNextReader(IndexReader reader) { + lookup.setNextReader(reader); + } + + @Override + public void setNextDocId(int doc) { + lookup.setNextDocId(doc); + } + + @Override + public void setNextScore(float score) { + ScriptableObject.putProperty(scope, "_score", score); + } + + @Override + public void setNextVar(String name, Object value) { + ScriptableObject.putProperty(scope, name, value); + } + + @Override + public void setNextSource(Map source) { + lookup.source().setNextSource(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 new NativeMap(scope, (Map) javaObject); + } + if (javaObject instanceof List) { + return new NativeList(scope, (List) javaObject); + } + return super.wrapAsJavaObject(cx, scope, javaObject, staticType); + } + } +} diff --git a/src/main/java/org/elasticsearch/script/javascript/support/NativeList.java b/src/main/java/org/elasticsearch/script/javascript/support/NativeList.java new file mode 100644 index 00000000000..c2345dc2bfc --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/support/NativeList.java @@ -0,0 +1,207 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.Undefined; +import org.mozilla.javascript.Wrapper; + +import java.util.List; + +/** + * + */ +public class NativeList implements Scriptable, Wrapper { + private static final long serialVersionUID = 3664761893203964569L; + + private List list; + private Scriptable parentScope; + private Scriptable prototype; + + + public static NativeList wrap(Scriptable scope, List list) { + return new NativeList(scope, list); + } + + public NativeList(Scriptable scope, List list) { + this.parentScope = scope; + 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".equals(name)) { + return list.size(); + } else { + return Undefined.instance; + } + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + + public Object get(int index, Scriptable start) { + if (index < 0 || index >= list.size()) { + 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) { + if ("length".equals(name)) { + return true; + } + return false; + } + + /* (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(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + + @SuppressWarnings("unchecked") + public void put(String name, Scriptable start, Object value) { + // do nothing here... + } + + /* (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(java.lang.String) + */ + + public void delete(String name) { + // nothing here + } + + /* (non-Javadoc) + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + + public void delete(int index) { + list.remove(index); + } + + /* (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() { + int size = list.size(); + Object[] ids = new Object[size]; + for (int i = 0; i < size; ++i) { + ids[i] = i; + } + return ids; + } + + /* (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 List.class.isInstance(instance); + } + +} diff --git a/src/main/java/org/elasticsearch/script/javascript/support/NativeMap.java b/src/main/java/org/elasticsearch/script/javascript/support/NativeMap.java new file mode 100644 index 00000000000..f36ce23a8b3 --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/support/NativeMap.java @@ -0,0 +1,223 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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 map; + private Scriptable parentScope; + private Scriptable prototype; + + + /** + * Construct + * + * @param scope + * @param map + * @return native map + */ + public static NativeMap wrap(Scriptable scope, Map map) { + return new NativeMap(scope, map); + } + + /** + * Construct + * + * @param scope + * @param map + */ + public NativeMap(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 "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); + } + +} diff --git a/src/main/java/org/elasticsearch/script/javascript/support/ScriptValueConverter.java b/src/main/java/org/elasticsearch/script/javascript/support/ScriptValueConverter.java new file mode 100644 index 00000000000..e7bedb40487 --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/support/ScriptValueConverter.java @@ -0,0 +1,183 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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. + *

+ * 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 propValues = new ArrayList(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 propValues = new HashMap(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 list = new ArrayList(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 map = (Map) value; + Map copyMap = new HashMap(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 collection = (Collection) 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 = new NativeMap(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; + } +} diff --git a/src/main/java/org/elasticsearch/script/javascript/support/ScriptableLinkedHashMap.java b/src/main/java/org/elasticsearch/script/javascript/support/ScriptableLinkedHashMap.java new file mode 100644 index 00000000000..88d34d7251e --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/support/ScriptableLinkedHashMap.java @@ -0,0 +1,188 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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 extends LinkedHashMap implements ScriptableMap { + private static final long serialVersionUID = 3774167893214964123L; + + private Scriptable parentScope; + private Scriptable prototype; + + + public ScriptableLinkedHashMap() { + } + + public ScriptableLinkedHashMap(int initialCapacity) { + super(initialCapacity); + } + + public ScriptableLinkedHashMap(Map 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; + } +} + diff --git a/src/main/java/org/elasticsearch/script/javascript/support/ScriptableMap.java b/src/main/java/org/elasticsearch/script/javascript/support/ScriptableMap.java new file mode 100644 index 00000000000..a7319c26027 --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/support/ScriptableMap.java @@ -0,0 +1,32 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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 extends Scriptable, Map { +} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/script/javascript/support/ScriptableWrappedMap.java b/src/main/java/org/elasticsearch/script/javascript/support/ScriptableWrappedMap.java new file mode 100644 index 00000000000..e78a92464df --- /dev/null +++ b/src/main/java/org/elasticsearch/script/javascript/support/ScriptableWrappedMap.java @@ -0,0 +1,342 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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. + *

+ *

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 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()); + } +} diff --git a/src/main/resources/es-plugin.properties b/src/main/resources/es-plugin.properties new file mode 100644 index 00000000000..e88bdca70c8 --- /dev/null +++ b/src/main/resources/es-plugin.properties @@ -0,0 +1 @@ +plugin=org.elasticsearch.plugin.javascript.JavaScriptPlugin diff --git a/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java b/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java new file mode 100644 index 00000000000..fe59d4aac57 --- /dev/null +++ b/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineTests.java @@ -0,0 +1,174 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.Lists; +import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.script.ExecutableScript; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +/** + * + */ +public class JavaScriptScriptEngineTests { + + private JavaScriptScriptEngineService se; + + @BeforeClass + public void setup() { + se = new JavaScriptScriptEngineService(ImmutableSettings.Builder.EMPTY_SETTINGS); + } + + @AfterClass + public void close() { + se.close(); + } + + @Test + public void testSimpleEquation() { + Map vars = new HashMap(); + Object o = se.execute(se.compile("1 + 2"), vars); + assertThat(((Number) o).intValue(), equalTo(3)); + } + + @Test + public void testMapAccess() { + Map vars = new HashMap(); + + Map obj2 = MapBuilder.newMapBuilder().put("prop2", "value2").map(); + Map obj1 = MapBuilder.newMapBuilder().put("prop1", "value1").put("obj2", obj2).put("l", Lists.newArrayList("2", "1")).map(); + vars.put("obj1", obj1); + Object o = se.execute(se.compile("obj1"), vars); + assertThat(o, instanceOf(Map.class)); + obj1 = (Map) o; + assertThat((String) obj1.get("prop1"), equalTo("value1")); + assertThat((String) ((Map) 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 vars = new HashMap(); + 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) obj1.get("obj2")).get("prop2"), equalTo("value2")); + } + + @Test + public void testJavaScriptObjectMapInter() { + Map vars = new HashMap(); + Map ctx = new HashMap(); + Map obj1 = new HashMap(); + 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) se.unwrap(vars.get("ctx")); + assertThat(ctx.containsKey("obj1"), equalTo(true)); + assertThat((String) ((Map) ctx.get("obj1")).get("prop1"), equalTo("uvalue1")); + assertThat(ctx.containsKey("obj2"), equalTo(true)); + assertThat((String) ((Map) ctx.get("obj2")).get("prop2"), equalTo("value2")); + } + + @Test + public void testJavaScriptInnerArrayCreation() { + Map ctx = new HashMap(); + Map doc = new HashMap(); + ctx.put("doc", doc); + + Object complied = se.compile("ctx.doc.field1 = ['value1', 'value2']"); + ExecutableScript script = se.executable(complied, new HashMap()); + script.setNextVar("ctx", ctx); + script.run(); + + Map unwrap = (Map) script.unwrap(ctx); + + assertThat(((Map) unwrap.get("doc")).get("field1"), instanceOf(List.class)); + } + + @Test + public void testAccessListInScript() { + Map vars = new HashMap(); + Map obj2 = MapBuilder.newMapBuilder().put("prop2", "value2").map(); + Map obj1 = MapBuilder.newMapBuilder().put("prop1", "value1").put("obj2", obj2).map(); + vars.put("l", Lists.newArrayList("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) o; + assertThat((String) obj1.get("prop1"), equalTo("value1")); + assertThat((String) ((Map) 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 vars = new HashMap(); + Map ctx = new HashMap(); + 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 vars = new HashMap(); + 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)); + } +} diff --git a/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptMultiThreadedTest.java b/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptMultiThreadedTest.java new file mode 100644 index 00000000000..9cbdc2cc782 --- /dev/null +++ b/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptMultiThreadedTest.java @@ -0,0 +1,174 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.util.concurrent.jsr166y.ThreadLocalRandom; +import org.elasticsearch.script.ExecutableScript; +import org.testng.annotations.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +/** + * + */ +@Test +public class JavaScriptScriptMultiThreadedTest { + + protected final ESLogger logger = Loggers.getLogger(getClass()); + + @Test + public void testExecutableNoRuntimeParams() throws Exception { + final JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(ImmutableSettings.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 vars = new HashMap(); + 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(ImmutableSettings.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 vars = new HashMap(); + 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(ImmutableSettings.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 runtimeVars = new HashMap(); + 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)); + } +} diff --git a/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptSearchTests.java b/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptSearchTests.java new file mode 100644 index 00000000000..110fe81709a --- /dev/null +++ b/src/test/java/org/elasticsearch/script/javascript/JavaScriptScriptSearchTests.java @@ -0,0 +1,267 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.client.Client; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.network.NetworkUtils; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.node.Node; +import org.elasticsearch.node.NodeBuilder; +import org.elasticsearch.search.sort.SortOrder; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.client.Requests.*; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.index.query.FilterBuilders.scriptFilter; +import static org.elasticsearch.index.query.QueryBuilders.*; +import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +/** + * + */ +@Test +public class JavaScriptScriptSearchTests { + + protected final ESLogger logger = Loggers.getLogger(getClass()); + + private Node node; + + private Client client; + + @BeforeMethod + public void createNodes() throws Exception { + node = NodeBuilder.nodeBuilder().settings(ImmutableSettings.settingsBuilder() + .put("cluster.name", "test-cluster-" + NetworkUtils.getLocalAddress()) + .put("gateway.type", "none") + .put("number_of_shards", 1)).node(); + client = node.client(); + } + + @AfterMethod + public void closeNodes() { + client.close(); + node.close(); + } + + @Test + public void testJavaScriptFilter() throws Exception { + client.admin().indices().prepareCreate("test").execute().actionGet(); + client.prepareIndex("test", "type1", "1") + .setSource(jsonBuilder().startObject().field("test", "value beck").field("num1", 1.0f).endObject()) + .execute().actionGet(); + client.admin().indices().prepareFlush().execute().actionGet(); + client.prepareIndex("test", "type1", "2") + .setSource(jsonBuilder().startObject().field("test", "value beck").field("num1", 2.0f).endObject()) + .execute().actionGet(); + client.admin().indices().prepareFlush().execute().actionGet(); + client.prepareIndex("test", "type1", "3") + .setSource(jsonBuilder().startObject().field("test", "value beck").field("num1", 3.0f).endObject()) + .execute().actionGet(); + client.admin().indices().refresh(refreshRequest()).actionGet(); + + logger.info("running doc['num1'].value > 1"); + SearchResponse response = client.prepareSearch() + .setQuery(filteredQuery(matchAllQuery(), scriptFilter("doc['num1'].value > 1").lang("js"))) + .addSort("num1", SortOrder.ASC) + .addScriptField("sNum1", "js", "doc['num1'].value", null) + .execute().actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + assertThat(response.hits().getAt(0).id(), equalTo("2")); + assertThat((Double) response.hits().getAt(0).fields().get("sNum1").values().get(0), equalTo(2.0)); + assertThat(response.hits().getAt(1).id(), equalTo("3")); + assertThat((Double) response.hits().getAt(1).fields().get("sNum1").values().get(0), equalTo(3.0)); + + logger.info("running doc['num1'].value > param1"); + response = client.prepareSearch() + .setQuery(filteredQuery(matchAllQuery(), scriptFilter("doc['num1'].value > param1").lang("js").addParam("param1", 2))) + .addSort("num1", SortOrder.ASC) + .addScriptField("sNum1", "js", "doc['num1'].value", null) + .execute().actionGet(); + + assertThat(response.hits().totalHits(), equalTo(1l)); + assertThat(response.hits().getAt(0).id(), equalTo("3")); + assertThat((Double) response.hits().getAt(0).fields().get("sNum1").values().get(0), equalTo(3.0)); + + logger.info("running doc['num1'].value > param1"); + response = client.prepareSearch() + .setQuery(filteredQuery(matchAllQuery(), scriptFilter("doc['num1'].value > param1").lang("js").addParam("param1", -1))) + .addSort("num1", SortOrder.ASC) + .addScriptField("sNum1", "js", "doc['num1'].value", null) + .execute().actionGet(); + + assertThat(response.hits().totalHits(), equalTo(3l)); + assertThat(response.hits().getAt(0).id(), equalTo("1")); + assertThat((Double) response.hits().getAt(0).fields().get("sNum1").values().get(0), equalTo(1.0)); + assertThat(response.hits().getAt(1).id(), equalTo("2")); + assertThat((Double) response.hits().getAt(1).fields().get("sNum1").values().get(0), equalTo(2.0)); + assertThat(response.hits().getAt(2).id(), equalTo("3")); + assertThat((Double) response.hits().getAt(2).fields().get("sNum1").values().get(0), equalTo(3.0)); + } + + @Test + public void testScriptFieldUsingSource() throws Exception { + client.admin().indices().prepareCreate("test").execute().actionGet(); + client.prepareIndex("test", "type1", "1") + .setSource(jsonBuilder().startObject() + .startObject("obj1").field("test", "something").endObject() + .startObject("obj2").startArray("arr2").value("arr_value1").value("arr_value2").endArray().endObject() + .endObject()) + .execute().actionGet(); + client.admin().indices().refresh(refreshRequest()).actionGet(); + + SearchResponse response = client.prepareSearch() + .setQuery(matchAllQuery()) + .addField("_source.obj1") // we also automatically detect _source in fields + .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 sObj1 = (Map) response.hits().getAt(0).field("_source.obj1").value(); + assertThat(sObj1.get("test").toString(), equalTo("something")); + assertThat(response.hits().getAt(0).field("s_obj1_test").value().toString(), equalTo("something")); + + sObj1 = (Map) response.hits().getAt(0).field("s_obj1").value(); + assertThat(sObj1.get("test").toString(), equalTo("something")); + assertThat(response.hits().getAt(0).field("s_obj1_test").value().toString(), equalTo("something")); + + Map sObj2 = (Map) response.hits().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.hits().getAt(0).field("s_obj2_arr2").value(); + 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 { + // execute a search before we create an index + try { + client.prepareSearch().setQuery(termQuery("test", "value")).execute().actionGet(); + assert false : "should fail"; + } catch (Exception e) { + // ignore, no indices + } + + try { + client.prepareSearch("test").setQuery(termQuery("test", "value")).execute().actionGet(); + assert false : "should fail"; + } catch (Exception e) { + // ignore, no indices + } + + client.admin().indices().create(createIndexRequest("test")).actionGet(); + client.index(indexRequest("test").type("type1").id("1") + .source(jsonBuilder().startObject().field("test", "value beck").field("num1", 1.0f).endObject())).actionGet(); + client.index(indexRequest("test").type("type1").id("2") + .source(jsonBuilder().startObject().field("test", "value check").field("num1", 2.0f).endObject())).actionGet(); + client.admin().indices().refresh(refreshRequest()).actionGet(); + + 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(customScoreQuery(termQuery("test", "value")).script("doc['num1'].value").lang("js"))) + ).actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation()); + assertThat(response.hits().getAt(0).id(), equalTo("2")); + assertThat(response.hits().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(customScoreQuery(termQuery("test", "value")).script("-doc['num1'].value").lang("js"))) + ).actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation()); + assertThat(response.hits().getAt(0).id(), equalTo("1")); + assertThat(response.hits().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(customScoreQuery(termQuery("test", "value")).script("Math.pow(doc['num1'].value, 2)").lang("js"))) + ).actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation()); + assertThat(response.hits().getAt(0).id(), equalTo("2")); + assertThat(response.hits().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(customScoreQuery(termQuery("test", "value")).script("Math.max(doc['num1'].value, 1)").lang("js"))) + ).actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation()); + assertThat(response.hits().getAt(0).id(), equalTo("2")); + assertThat(response.hits().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(customScoreQuery(termQuery("test", "value")).script("doc['num1'].value * _score").lang("js"))) + ).actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation()); + assertThat(response.hits().getAt(0).id(), equalTo("2")); + assertThat(response.hits().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(customScoreQuery(termQuery("test", "value")).script("param1 * param2 * _score").param("param1", 2).param("param2", 2).lang("js"))) + ).actionGet(); + + assertThat(response.hits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.hits().getAt(0).id(), response.hits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.hits().getAt(1).id(), response.hits().getAt(1).explanation()); + } +} diff --git a/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java b/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java new file mode 100644 index 00000000000..fdaa6554613 --- /dev/null +++ b/src/test/java/org/elasticsearch/script/javascript/SimpleBench.java @@ -0,0 +1,71 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.ImmutableSettings; +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(ImmutableSettings.Builder.EMPTY_SETTINGS); + Object compiled = se.compile("x + y"); + + Map vars = new HashMap(); + // 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 entry : vars.entrySet()) { + executableScript.setNextVar(entry.getKey(), entry.getValue()); + } + executableScript.run(); + } + System.out.println("Executable (vars) Took: " + stopWatch.stop().lastTaskTime()); + } +} diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 00000000000..497c97f9959 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=INFO, out + +log4j.appender.out=org.apache.log4j.ConsoleAppender +log4j.appender.out.layout=org.apache.log4j.PatternLayout +log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n