[transform] added a new script transform

Enables manipulating existing payload and building a new payload based on a script

Original commit: elastic/x-pack-elasticsearch@912dafe709
This commit is contained in:
uboness 2015-02-25 15:56:56 +02:00
parent 7ab8271692
commit 46f6572756
3 changed files with 350 additions and 0 deletions

View File

@ -0,0 +1,161 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.alerts.support;
import org.elasticsearch.alerts.AlertsException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ScriptService;
import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
/**
*
*/
public class Script implements ToXContent {
public static final ParseField SCRIPT_FIELD = new ParseField("script");
public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField LANG_FIELD = new ParseField("lang");
public static final ParseField PARAMS_FIELD = new ParseField("params");
private final String script;
private final ScriptService.ScriptType type;
private final String lang;
private final Map<String, Object> params;
public Script(String script) {
this(script, ScriptService.ScriptType.INLINE, ScriptService.DEFAULT_LANG, Collections.<String, Object>emptyMap());
}
public Script(String script, ScriptService.ScriptType type, String lang, Map<String, Object> params) {
this.script = script;
this.type = type;
this.lang = lang;
this.params = params;
}
public String script() {
return script;
}
public ScriptService.ScriptType type() {
return type;
}
public String lang() {
return lang;
}
public Map<String, Object> params() {
return params;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Script script1 = (Script) o;
if (!lang.equals(script1.lang)) return false;
if (!params.equals(script1.params)) return false;
if (!script.equals(script1.script)) return false;
if (type != script1.type) return false;
return true;
}
@Override
public int hashCode() {
int result = script.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + lang.hashCode();
result = 31 * result + params.hashCode();
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field(SCRIPT_FIELD.getPreferredName(), script)
.field(TYPE_FIELD.getPreferredName(), script)
.field(LANG_FIELD.getPreferredName(), lang)
.field(PARAMS_FIELD.getPreferredName(), params)
.endObject();
}
public static Script parse(XContentParser parser) throws IOException {
XContentParser.Token token = parser.currentToken();
if (token == XContentParser.Token.VALUE_STRING) {
return new Script(parser.text());
}
if (token != XContentParser.Token.START_OBJECT) {
throw new ParseException("expected a string value or an object, but found [" + token + "] instead");
}
String script = null;
ScriptService.ScriptType type = ScriptService.ScriptType.INLINE;
String lang = ScriptService.DEFAULT_LANG;
Map<String, Object> params = Collections.emptyMap();
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (SCRIPT_FIELD.match(currentFieldName)) {
if (token == XContentParser.Token.VALUE_STRING) {
script = parser.text();
} else {
throw new ParseException("expected a string value for field [" + currentFieldName + "], but found [" + token + "]");
}
} else if (TYPE_FIELD.match(currentFieldName)) {
if (token == XContentParser.Token.VALUE_STRING) {
String value = parser.text();
try {
type = ScriptService.ScriptType.valueOf(value.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
throw new ParseException("unknown script type [" + value + "]");
}
}
} else if (LANG_FIELD.match(currentFieldName)) {
if (token == XContentParser.Token.VALUE_STRING) {
lang = parser.text();
} else {
throw new ParseException("expected a string value for field [" + currentFieldName + "], but found [" + token + "]");
}
} else if (PARAMS_FIELD.match(currentFieldName)) {
if (token == XContentParser.Token.START_OBJECT) {
params = parser.map();
} else {
throw new ParseException("expected an object for field [" + currentFieldName + "], but found [" + token + "]");
}
} else {
throw new ParseException("unexpected field [" + currentFieldName + "]");
}
}
if (script == null) {
throw new ParseException("missing required string field [" + currentFieldName + "]");
}
return new Script(script, type, lang, params);
}
public static class ParseException extends AlertsException {
public ParseException(String msg) {
super(msg);
}
public ParseException(String msg, Throwable cause) {
super(msg, cause);
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.alerts.transform;
import org.elasticsearch.alerts.AlertsSettingsException;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.support.Script;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ExecutableScript;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class ScriptTransform extends Transform {
public static final String TYPE = "script";
private final Script script;
private final ScriptServiceProxy scriptService;
public ScriptTransform(Script script, ScriptServiceProxy scriptService) {
this.script = script;
this.scriptService = scriptService;
}
@Override
public String type() {
return TYPE;
}
Script script() {
return script;
}
@Override
public Result apply(ExecutionContext ctx, Payload payload) throws IOException {
Map<String, Object> model = new HashMap<>();
model.putAll(script.params());
model.putAll(createModel(ctx, payload));
ExecutableScript executable = scriptService.executable(script.lang(), script.script(), script.type(), model);
Object value = executable.run();
if (!(value instanceof Map)) {
throw new TransformException("illegal [script] transform [" + script.script() + "]. script must output a Map<String, Object> structure but outputted [" + value.getClass().getSimpleName() + "] instead");
}
return new Result(TYPE, new Payload.Simple((Map<String, Object>) value));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.value(script);
}
public static class Parser implements Transform.Parser<ScriptTransform> {
private final ScriptServiceProxy scriptService;
@Inject
public Parser(ScriptServiceProxy scriptService) {
this.scriptService = scriptService;
}
@Override
public String type() {
return TYPE;
}
@Override
public ScriptTransform parse(XContentParser parser) throws IOException {
Script script = null;
try {
script = Script.parse(parser);
} catch (Script.ParseException pe) {
throw new AlertsSettingsException("could not parse [script] transform", pe);
}
return new ScriptTransform(script, scriptService);
}
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.alerts.transform;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.support.Script;
import org.elasticsearch.alerts.support.Variables;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.util.Collections;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class ScriptTransformTests extends ElasticsearchTestCase {
@Test
public void testApply() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptService.ScriptType type = randomFrom(ScriptService.ScriptType.values());
Map<String, Object> params = Collections.emptyMap();
Script script = new Script("_script", type, "_lang", params);
ScriptTransform transform = new ScriptTransform(script, service);
DateTime now = new DateTime();
ExecutionContext ctx = mock(ExecutionContext.class);
when(ctx.scheduledTime()).thenReturn(now);
when(ctx.fireTime()).thenReturn(now);
Payload payload = new Payload.Simple(ImmutableMap.<String, Object>builder().put("key", "value").build());
Map<String, Object> model = ImmutableMap.<String, Object>builder()
.put(Variables.PAYLOAD, payload.data())
.put(Variables.FIRE_TIME, now)
.put(Variables.SCHEDULED_FIRE_TIME, now)
.build();
Map<String, Object> transformed = ImmutableMap.<String, Object>builder()
.put("key", "value")
.build();
ExecutableScript executable = mock(ExecutableScript.class);
when(executable.run()).thenReturn(transformed);
when(service.executable("_lang", "_script", type, model)).thenReturn(executable);
Transform.Result result = transform.apply(ctx, payload);
assertThat(result, notNullValue());
assertThat(result.type(), is(ScriptTransform.TYPE));
assertThat(result.payload().data(), equalTo(transformed));
}
@Test
public void testParser() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class);
ScriptService.ScriptType type = randomFrom(ScriptService.ScriptType.values());
XContentBuilder builder = jsonBuilder().startObject()
.field("script", "_script")
.field("lang", "_lang")
.field("type", type.name())
.startObject("params").field("key", "value").endObject()
.endObject();
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
ScriptTransform transform = new ScriptTransform.Parser(service).parse(parser);
assertThat(transform.script(), equalTo(new Script("_script", type, "_lang", ImmutableMap.<String, Object>builder().put("key", "value").build())));
}
@Test
public void testParser_String() throws Exception {
ScriptServiceProxy service = mock(ScriptServiceProxy.class);
XContentBuilder builder = jsonBuilder().value("_script");
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
ScriptTransform transform = new ScriptTransform.Parser(service).parse(parser);
assertThat(transform.script(), equalTo(new Script("_script", ScriptService.ScriptType.INLINE, ScriptService.DEFAULT_LANG, ImmutableMap.<String, Object>of())));
}
}