JavaScript Plugin: Allow to use javascript for scripts, closes #401.
This commit is contained in:
parent
5a7c8fe2cb
commit
5b8bc333bd
|
@ -66,6 +66,7 @@
|
|||
<w>ints</w>
|
||||
<w>iter</w>
|
||||
<w>iterable</w>
|
||||
<w>javascript</w>
|
||||
<w>javax</w>
|
||||
<w>jclouds</w>
|
||||
<w>jgroups</w>
|
||||
|
@ -114,6 +115,7 @@
|
|||
<w>routings</w>
|
||||
<w>rsts</w>
|
||||
<w>sbuf</w>
|
||||
<w>scriptable</w>
|
||||
<w>searchable</w>
|
||||
<w>segs</w>
|
||||
<w>serializers</w>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/plugin-analysis-icu.iml" filepath="$PROJECT_DIR$/.idea/modules/plugin-analysis-icu.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules//plugin-cloud-aws.iml" filepath="$PROJECT_DIR$/.idea/modules//plugin-cloud-aws.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/plugin-lang-groovy.iml" filepath="$PROJECT_DIR$/.idea/modules/plugin-lang-groovy.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules//plugin-lang-javascript.iml" filepath="$PROJECT_DIR$/.idea/modules//plugin-lang-javascript.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules//plugin-mapper-attachments.iml" filepath="$PROJECT_DIR$/.idea/modules//plugin-mapper-attachments.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules//plugin-river-couchdb.iml" filepath="$PROJECT_DIR$/.idea/modules//plugin-river-couchdb.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules//plugin-river-rabbitmq.iml" filepath="$PROJECT_DIR$/.idea/modules//plugin-river-rabbitmq.iml" />
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="elasticsearch" />
|
||||
<orderEntry type="module" module-name="plugin-lang-groovy" />
|
||||
<orderEntry type="module" module-name="plugin-lang-javascript" />
|
||||
<orderEntry type="module" module-name="plugin-mapper-attachments" />
|
||||
<orderEntry type="module" module-name="plugin-transport-memcached" />
|
||||
<orderEntry type="module" module-name="plugin-transport-thrift" />
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/../../plugins/lang/javascript/build/classes/main" />
|
||||
<output-test url="file://$MODULE_DIR$/../../plugins/lang/javascript/build/classes/test" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/../../plugins/lang/javascript">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../plugins/lang/javascript/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../plugins/lang/javascript/src/test/java" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="elasticsearch" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="rhino">
|
||||
<CLASSES>
|
||||
<root url="jar://$GRADLE_REPOSITORY$/rhino/js/jars/js-1.7R2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module" module-name="test-testng" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="testng" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -39,5 +39,5 @@ public interface ExecutableScript {
|
|||
/**
|
||||
* Executes the script.
|
||||
*/
|
||||
Object run(Map vars);
|
||||
Object run(Map<String, Object> vars);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public interface ScriptEngineService {
|
|||
|
||||
Object compile(String script);
|
||||
|
||||
ExecutableScript executable(Object compiledScript, Map vars);
|
||||
ExecutableScript executable(Object compiledScript, Map<String, Object> vars);
|
||||
|
||||
Object execute(Object compiledScript, Map vars);
|
||||
Object execute(Object compiledScript, Map<String, Object> vars);
|
||||
}
|
||||
|
|
|
@ -56,8 +56,7 @@ public class CustomScoreSearchTests extends AbstractNodesTests {
|
|||
return client("server1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomScriptBoost() throws Exception {
|
||||
@Test public void testCustomScriptBoost() throws Exception {
|
||||
// execute a search before we create an index
|
||||
try {
|
||||
client.prepareSearch().setQuery(termQuery("test", "value")).execute().actionGet();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
|
|||
return loader.parseClass(script, generateScriptName());
|
||||
}
|
||||
|
||||
@Override public ExecutableScript executable(Object compiledScript, Map vars) {
|
||||
@Override public ExecutableScript executable(Object compiledScript, Map<String, Object> vars) {
|
||||
try {
|
||||
Class scriptClass = (Class) compiledScript;
|
||||
Script scriptObject = (Script) scriptClass.newInstance();
|
||||
|
@ -69,7 +69,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
|
|||
}
|
||||
}
|
||||
|
||||
@Override public Object execute(Object compiledScript, Map vars) {
|
||||
@Override public Object execute(Object compiledScript, Map<String, Object> vars) {
|
||||
try {
|
||||
Class scriptClass = (Class) compiledScript;
|
||||
Script scriptObject = (Script) scriptClass.newInstance();
|
||||
|
@ -97,7 +97,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
|
|||
return script.run();
|
||||
}
|
||||
|
||||
@Override public Object run(Map vars) {
|
||||
@Override public Object run(Map<String, Object> vars) {
|
||||
script.getBinding().getVariables().putAll(vars);
|
||||
return script.run();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.script.groovy;
|
||||
|
||||
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;
|
||||
|
@ -38,6 +39,7 @@ import static org.elasticsearch.client.Requests.*;
|
|||
import static org.elasticsearch.common.xcontent.XContentFactory.*;
|
||||
import static org.elasticsearch.index.query.xcontent.FilterBuilders.*;
|
||||
import static org.elasticsearch.index.query.xcontent.QueryBuilders.*;
|
||||
import static org.elasticsearch.search.builder.SearchSourceBuilder.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
|
@ -155,4 +157,101 @@ public class GroovyScriptSearchTests {
|
|||
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("groovy")))
|
||||
).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("groovy")))
|
||||
).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("groovy")))
|
||||
).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, 1d)").lang("groovy")))
|
||||
).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("groovy")))
|
||||
).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("groovy")))
|
||||
).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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public class SimpleBench {
|
|||
GroovyScriptEngineService se = new GroovyScriptEngineService(ImmutableSettings.Builder.EMPTY_SETTINGS);
|
||||
Object compiled = se.compile("x + y");
|
||||
|
||||
Map<String, Integer> vars = new HashMap<String, Integer>();
|
||||
Map<String, Object> vars = new HashMap<String, Object>();
|
||||
// warm up
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
vars.put("x", i);
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
dependsOn(':elasticsearch')
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven'
|
||||
|
||||
archivesBaseName = "elasticsearch-lang-javascript"
|
||||
|
||||
explodedDistDir = new File(distsDir, 'exploded')
|
||||
|
||||
configurations.compile.transitive = true
|
||||
configurations.testCompile.transitive = true
|
||||
|
||||
// no need to use the resource dir
|
||||
sourceSets.main.resources.srcDirs 'src/main/java'
|
||||
sourceSets.test.resources.srcDirs 'src/test/java'
|
||||
|
||||
// add the source files to the dist jar
|
||||
//jar {
|
||||
// from sourceSets.main.allSource
|
||||
//}
|
||||
|
||||
configurations {
|
||||
dists
|
||||
distLib {
|
||||
visible = false
|
||||
transitive = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':elasticsearch')
|
||||
|
||||
compile('rhino:js:1.7R2')
|
||||
distLib('rhino:js:1.7R2') { transitive = false }
|
||||
|
||||
testCompile project(':test-testng')
|
||||
testCompile('org.testng:testng:5.10:jdk15') { transitive = false }
|
||||
testCompile 'org.hamcrest:hamcrest-all:1.1'
|
||||
}
|
||||
|
||||
test {
|
||||
useTestNG()
|
||||
jmvArgs = ["-ea", "-Xmx1024m"]
|
||||
suiteName = project.name
|
||||
listeners = ["org.elasticsearch.util.testng.Listeners"]
|
||||
systemProperties["es.test.log.conf"] = System.getProperty("es.test.log.conf", "log4j-gradle.properties")
|
||||
}
|
||||
|
||||
task explodedDist(dependsOn: [jar], description: 'Builds the plugin zip file') << {
|
||||
[explodedDistDir]*.mkdirs()
|
||||
|
||||
copy {
|
||||
from configurations.distLib
|
||||
into explodedDistDir
|
||||
}
|
||||
|
||||
// remove elasticsearch files (compile above adds the elasticsearch one)
|
||||
ant.delete { fileset(dir: explodedDistDir, includes: "elasticsearch-*.jar") }
|
||||
|
||||
copy {
|
||||
from libsDir
|
||||
into explodedDistDir
|
||||
}
|
||||
|
||||
ant.delete { fileset(dir: explodedDistDir, includes: "elasticsearch-*-javadoc.jar") }
|
||||
ant.delete { fileset(dir: explodedDistDir, includes: "elasticsearch-*-sources.jar") }
|
||||
}
|
||||
|
||||
task zip(type: Zip, dependsOn: ['explodedDist']) {
|
||||
from(explodedDistDir) {
|
||||
}
|
||||
}
|
||||
|
||||
task release(dependsOn: [zip]) << {
|
||||
ant.delete(dir: explodedDistDir)
|
||||
copy {
|
||||
from distsDir
|
||||
into(new File(rootProject.distsDir, "plugins"))
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
deployerJars
|
||||
}
|
||||
|
||||
dependencies {
|
||||
deployerJars "org.apache.maven.wagon:wagon-http:1.0-beta-2"
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
jar {
|
||||
// from sourceSets.main.allJava
|
||||
manifest {
|
||||
attributes("Implementation-Title": "ElasticSearch", "Implementation-Version": rootProject.version, "Implementation-Date": buildTimeStr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
|
||||
uploadArchives {
|
||||
repositories.mavenDeployer {
|
||||
configuration = configurations.deployerJars
|
||||
repository(url: rootProject.mavenRepoUrl) {
|
||||
authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass)
|
||||
}
|
||||
snapshotRepository(url: rootProject.mavenSnapshotRepoUrl) {
|
||||
authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass)
|
||||
}
|
||||
|
||||
pom.project {
|
||||
inceptionYear '2009'
|
||||
name 'elasticsearch-plugins-lang-javascript'
|
||||
description 'JavaScript Plugin for ElasticSearch'
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache Software License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection 'git://github.com/elasticsearch/elasticsearch.git'
|
||||
developerConnection 'git@github.com:elasticsearch/elasticsearch.git'
|
||||
url 'http://github.com/elasticsearch/elasticsearch'
|
||||
}
|
||||
}
|
||||
|
||||
pom.whenConfigured {pom ->
|
||||
pom.dependencies = pom.dependencies.findAll {dep -> dep.scope != 'test' } // removes the test scoped ones
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
plugin=org.elasticsearch.plugin.javascript.JavaScriptPlugin
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.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.javascript.support.NativeMap;
|
||||
import org.elasticsearch.script.javascript.support.ScriptValueConverter;
|
||||
import org.mozilla.javascript.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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 String[] types() {
|
||||
return new String[]{"js", "javascript"};
|
||||
}
|
||||
|
||||
@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 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();
|
||||
}
|
||||
}
|
||||
|
||||
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 Object run(Map<String, Object> vars) {
|
||||
Context ctx = Context.enter();
|
||||
try {
|
||||
ctx.setWrapFactory(wrapFactory);
|
||||
for (Map.Entry<String, Object> entry : vars.entrySet()) {
|
||||
ScriptableObject.putProperty(scope, entry.getKey(), entry.getValue());
|
||||
}
|
||||
return ScriptValueConverter.unwrapValue(script.exec(ctx, scope));
|
||||
} finally {
|
||||
Context.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
return super.wrapAsJavaObject(cx, scope, javaObject, staticType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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<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 services Repository Services Registry
|
||||
* @param scope Scripting scope
|
||||
* @param qname QName of the property value for conversion
|
||||
* @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 = 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public interface ScriptableMap<K, V> extends Scriptable, Map<K, V> {
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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>Access should be by string key only - not integer index - unless you are sure the wrapped
|
||||
* map will maintain insertion order of the elements.
|
||||
*
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.ImmutableSettings;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class JavaScriptScriptEngineTests {
|
||||
|
||||
private JavaScriptScriptEngineService se;
|
||||
|
||||
@BeforeTest public void setup() {
|
||||
se = new JavaScriptScriptEngineService(ImmutableSettings.Builder.EMPTY_SETTINGS);
|
||||
}
|
||||
|
||||
@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 testMapPassedMapReturned() {
|
||||
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("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"));
|
||||
}
|
||||
|
||||
@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 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>();
|
||||
Map<String, Object> ctx = new HashMap<String, Object>();
|
||||
Object compiledScript = se.compile("value");
|
||||
|
||||
ExecutableScript script = se.executable(compiledScript, vars);
|
||||
ctx.put("value", 1);
|
||||
Object o = script.run(ctx);
|
||||
assertThat(((Number) o).intValue(), equalTo(1));
|
||||
|
||||
ctx.put("value", 2);
|
||||
o = script.run(ctx);
|
||||
assertThat(((Number) o).intValue(), equalTo(2));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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.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.*;
|
||||
import static org.elasticsearch.index.query.xcontent.FilterBuilders.*;
|
||||
import static org.elasticsearch.index.query.xcontent.QueryBuilders.*;
|
||||
import static org.elasticsearch.search.builder.SearchSourceBuilder.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
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("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(filtered(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(filtered(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(filtered(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<String, Object> sObj1 = (Map<String, Object>) 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<String, Object>) 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<String, Object> sObj2 = (Map<String, Object>) 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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Licensed to Elastic Search and Shay Banon under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Elastic Search 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;
|
||||
|
||||
/**
|
||||
* @author kimchy (shay.banon)
|
||||
*/
|
||||
public class SimpleBench {
|
||||
|
||||
public static void main(String[] args) {
|
||||
JavaScriptScriptEngineService se = new JavaScriptScriptEngineService(ImmutableSettings.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++) {
|
||||
executableScript.run(vars);
|
||||
}
|
||||
System.out.println("Executable (vars) Took: " + stopWatch.stop().lastTaskTime());
|
||||
}
|
||||
}
|
|
@ -12,7 +12,9 @@ include 'plugins-cloud-aws'
|
|||
include 'plugins-hadoop'
|
||||
include 'plugins-analysis-icu'
|
||||
include 'plugins-mapper-attachments'
|
||||
|
||||
include 'plugins-lang-groovy'
|
||||
include 'plugins-lang-javascript'
|
||||
|
||||
include 'plugins-transport-memcached'
|
||||
include 'plugins-transport-thrift'
|
||||
|
|
Loading…
Reference in New Issue