[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:
parent
7ab8271692
commit
46f6572756
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue