diff --git a/pom.xml b/pom.xml
index 2b45300f2a8..e4bdb41ee88 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,20 @@
test
+
+ org.mockito
+ mockito-core
+ 1.9.5
+ test
+
+
+
+ org.objenesis
+ objenesis
+ 2.1
+ test
+
+
com.google.guava
diff --git a/src/main/java/org/elasticsearch/alerts/AlertsModule.java b/src/main/java/org/elasticsearch/alerts/AlertsModule.java
index e7b27cfd9a4..721836b7488 100644
--- a/src/main/java/org/elasticsearch/alerts/AlertsModule.java
+++ b/src/main/java/org/elasticsearch/alerts/AlertsModule.java
@@ -13,6 +13,7 @@ import org.elasticsearch.alerts.rest.AlertsRestModule;
import org.elasticsearch.alerts.scheduler.SchedulerModule;
import org.elasticsearch.alerts.support.TemplateUtils;
import org.elasticsearch.alerts.support.init.InitializingModule;
+import org.elasticsearch.alerts.support.template.TemplateModule;
import org.elasticsearch.alerts.transform.TransformModule;
import org.elasticsearch.alerts.transport.AlertsTransportModule;
import org.elasticsearch.alerts.condition.ConditionModule;
@@ -28,6 +29,7 @@ public class AlertsModule extends AbstractModule implements SpawnModules {
public Iterable extends Module> spawnModules() {
return ImmutableList.of(
new InitializingModule(),
+ new TemplateModule(),
new AlertsClientModule(),
new TransformModule(),
new AlertsRestModule(),
diff --git a/src/main/java/org/elasticsearch/alerts/actions/Action.java b/src/main/java/org/elasticsearch/alerts/actions/Action.java
index 5acff70ef89..63b83a22be7 100644
--- a/src/main/java/org/elasticsearch/alerts/actions/Action.java
+++ b/src/main/java/org/elasticsearch/alerts/actions/Action.java
@@ -8,6 +8,7 @@ package org.elasticsearch.alerts.actions;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -19,8 +20,8 @@ import java.io.IOException;
*/
public abstract class Action implements ToXContent {
- public static final String ALERT_NAME_VARIABLE_NAME = "alert_name";
- public static final String RESPONSE_VARIABLE_NAME = "response";
+ public static final String ALERT_NAME_VARIABLE = "alert_name";
+ public static final String PAYLOAD_VARIABLE = "payload";
protected final ESLogger logger;
@@ -38,7 +39,12 @@ public abstract class Action implements ToXContent {
*/
public abstract R execute(ExecutionContext context, Payload payload) throws IOException;
-
+ protected static ImmutableMap templateModel(ExecutionContext ctx, Payload payload) {
+ return ImmutableMap.builder()
+ .put(ALERT_NAME_VARIABLE, ctx.alert().name())
+ .put(PAYLOAD_VARIABLE, payload.data())
+ .build();
+ }
/**
* Parses xcontent to a concrete action of the same type.
*/
@@ -57,8 +63,6 @@ public abstract class Action implements ToXContent {
R parseResult(XContentParser parser) throws IOException;
}
-
-
public static abstract class Result implements ToXContent {
public static final ParseField SUCCESS_FIELD = new ParseField("success");
diff --git a/src/main/java/org/elasticsearch/alerts/actions/ActionSettingsException.java b/src/main/java/org/elasticsearch/alerts/actions/ActionSettingsException.java
new file mode 100644
index 00000000000..08dd19018d5
--- /dev/null
+++ b/src/main/java/org/elasticsearch/alerts/actions/ActionSettingsException.java
@@ -0,0 +1,20 @@
+/*
+ * 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.actions;
+
+/**
+ *
+ */
+public class ActionSettingsException extends ActionException {
+
+ public ActionSettingsException(String msg) {
+ super(msg);
+ }
+
+ public ActionSettingsException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/src/main/java/org/elasticsearch/alerts/actions/email/EmailAction.java b/src/main/java/org/elasticsearch/alerts/actions/email/EmailAction.java
index 049560b4568..b4794f59d33 100644
--- a/src/main/java/org/elasticsearch/alerts/actions/email/EmailAction.java
+++ b/src/main/java/org/elasticsearch/alerts/actions/email/EmailAction.java
@@ -8,9 +8,11 @@ package org.elasticsearch.alerts.actions.email;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.actions.Action;
+import org.elasticsearch.alerts.actions.ActionSettingsException;
import org.elasticsearch.alerts.actions.email.service.*;
-import org.elasticsearch.alerts.support.StringTemplateUtils;
+import org.elasticsearch.alerts.support.template.Template;
import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
@@ -20,8 +22,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
/**
*/
@@ -33,21 +33,18 @@ public class EmailAction extends Action {
private final Authentication auth;
private final Profile profile;
private final String account;
- private final StringTemplateUtils.Template subject;
- private final StringTemplateUtils.Template textBody;
- private final StringTemplateUtils.Template htmlBody;
+ private final Template subject;
+ private final Template textBody;
+ private final Template htmlBody;
private final boolean attachPayload;
private final EmailService emailService;
- private final StringTemplateUtils templateUtils;
- public EmailAction(ESLogger logger, EmailService emailService, StringTemplateUtils templateUtils,
- Email.Builder email, Authentication auth, Profile profile, String account,
- StringTemplateUtils.Template subject, StringTemplateUtils.Template textBody,
- StringTemplateUtils.Template htmlBody, boolean attachPayload) {
+ public EmailAction(ESLogger logger, EmailService emailService, Email.Builder email, Authentication auth, Profile profile,
+ String account, Template subject, Template textBody, Template htmlBody, boolean attachPayload) {
+
super(logger);
this.emailService = emailService;
- this.templateUtils = templateUtils;
this.email = email;
this.auth = auth;
this.profile = profile;
@@ -65,21 +62,14 @@ public class EmailAction extends Action {
@Override
public Result execute(ExecutionContext ctx, Payload payload) throws IOException {
+ ImmutableMap model = templateModel(ctx, payload);
+
email.id(ctx.id());
-
- Map alertParams = new HashMap<>();
- alertParams.put(Action.ALERT_NAME_VARIABLE_NAME, ctx.alert().name());
- alertParams.put(RESPONSE_VARIABLE_NAME, payload.data());
-
- String text = templateUtils.executeTemplate(subject, alertParams);
- email.subject(text);
-
- text = templateUtils.executeTemplate(textBody, alertParams);
- email.textBody(text);
+ email.subject(subject.render(model));
+ email.textBody(textBody.render(model));
if (htmlBody != null) {
- text = templateUtils.executeTemplate(htmlBody, alertParams);
- email.htmlBody(text);
+ email.htmlBody(htmlBody.render(model));
}
if (attachPayload) {
@@ -100,17 +90,17 @@ public class EmailAction extends Action {
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (account != null) {
- builder.field(Parser.ACCOUNT_FIELD.getPreferredName(), account);
+ builder.field(EmailAction.Parser.ACCOUNT_FIELD.getPreferredName(), account);
}
if (profile != null) {
- builder.field(Parser.PROFILE_FIELD.getPreferredName(), profile);
+ builder.field(EmailAction.Parser.PROFILE_FIELD.getPreferredName(), profile);
}
builder.field(Email.TO_FIELD.getPreferredName(), (ToXContent) email.to());
if (subject != null) {
- StringTemplateUtils.writeTemplate(Email.SUBJECT_FIELD.getPreferredName(), subject, builder, params);
+ builder.field(Email.SUBJECT_FIELD.getPreferredName(), subject);
}
if (textBody != null) {
- StringTemplateUtils.writeTemplate(Email.TEXT_BODY_FIELD.getPreferredName(), textBody, builder, params);
+ builder.field(Email.TEXT_BODY_FIELD.getPreferredName(), textBody);
}
return builder.endObject();
}
@@ -125,14 +115,14 @@ public class EmailAction extends Action {
public static final ParseField EMAIL_FIELD = new ParseField("email");
public static final ParseField REASON_FIELD = new ParseField("reason");
- private final StringTemplateUtils templateUtils;
+ private final Template.Parser templateParser;
private final EmailService emailService;
@Inject
- public Parser(Settings settings, EmailService emailService, StringTemplateUtils templateUtils) {
+ public Parser(Settings settings, EmailService emailService, Template.Parser templateParser) {
super(settings);
this.emailService = emailService;
- this.templateUtils = templateUtils;
+ this.templateParser = templateParser;
}
@Override
@@ -147,9 +137,9 @@ public class EmailAction extends Action {
String account = null;
Profile profile = null;
Email.Builder email = Email.builder();
- StringTemplateUtils.Template subject = null;
- StringTemplateUtils.Template textBody = null;
- StringTemplateUtils.Template htmlBody = null;
+ Template subject = null;
+ Template textBody = null;
+ Template htmlBody = null;
boolean attachPayload = false;
String currentFieldName = null;
@@ -169,15 +159,27 @@ public class EmailAction extends Action {
} else if (Email.BCC_FIELD.match(currentFieldName)) {
email.bcc(Email.AddressList.parse(currentFieldName, token, parser));
} else if (Email.SUBJECT_FIELD.match(currentFieldName)) {
- subject = StringTemplateUtils.readTemplate(parser);
+ try {
+ subject = templateParser.parse(parser);
+ } catch (Template.Parser.ParseException pe) {
+ throw new ActionSettingsException("could not parse email [subject] template", pe);
+ }
} else if (Email.TEXT_BODY_FIELD.match(currentFieldName)) {
- textBody = StringTemplateUtils.readTemplate(parser);
+ try {
+ textBody = templateParser.parse(parser);
+ } catch (Template.Parser.ParseException pe) {
+ throw new ActionSettingsException("could not parse email [text_body] template", pe);
+ }
+ } else if (Email.HTML_BODY_FIELD.match(currentFieldName)) {
+ try {
+ htmlBody = templateParser.parse(parser);
+ } catch (Template.Parser.ParseException pe) {
+ throw new ActionSettingsException("could not parse email [html_body] template", pe);
+ }
} else if (token == XContentParser.Token.VALUE_STRING) {
if (Email.PRIORITY_FIELD.match(currentFieldName)) {
email.priority(Email.Priority.resolve(parser.text()));
- } else if (Email.HTML_BODY_FIELD.match(currentFieldName)) {
- htmlBody = StringTemplateUtils.readTemplate(parser);
- } else if (ACCOUNT_FIELD.match(currentFieldName)) {
+ } else if (ACCOUNT_FIELD.match(currentFieldName)) {
account = parser.text();
} else if (USER_FIELD.match(currentFieldName)) {
user = parser.text();
@@ -186,26 +188,26 @@ public class EmailAction extends Action {
} else if (PROFILE_FIELD.match(currentFieldName)) {
profile = Profile.resolve(parser.text());
} else {
- throw new EmailException("could not parse email action. unrecognized string field [" + currentFieldName + "]");
+ throw new ActionSettingsException("could not parse email action. unrecognized string field [" + currentFieldName + "]");
}
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if (ATTACH_PAYLOAD_FIELD.match(currentFieldName)) {
attachPayload = parser.booleanValue();
} else {
- throw new EmailException("could not parse email action. unrecognized boolean field [" + currentFieldName + "]");
+ throw new ActionSettingsException("could not parse email action. unrecognized boolean field [" + currentFieldName + "]");
}
} else {
- throw new EmailException("could not parse email action. unexpected token [" + token + "]");
+ throw new ActionSettingsException("could not parse email action. unexpected token [" + token + "]");
}
}
}
if (email.to() == null || email.to().isEmpty()) {
- throw new EmailException("could not parse email action. [to] was not found or was empty");
+ throw new ActionSettingsException("could not parse email action. [to] was not found or was empty");
}
Authentication auth = new Authentication(user, password);
- return new EmailAction(logger, emailService, templateUtils, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
+ return new EmailAction(logger, emailService, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
}
@Override
diff --git a/src/main/java/org/elasticsearch/alerts/actions/index/IndexAction.java b/src/main/java/org/elasticsearch/alerts/actions/index/IndexAction.java
index 95486756f90..c3821e919c1 100644
--- a/src/main/java/org/elasticsearch/alerts/actions/index/IndexAction.java
+++ b/src/main/java/org/elasticsearch/alerts/actions/index/IndexAction.java
@@ -12,6 +12,7 @@ import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.ActionException;
+import org.elasticsearch.alerts.actions.ActionSettingsException;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.component.AbstractComponent;
@@ -90,6 +91,26 @@ public class IndexAction extends Action {
return builder;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ IndexAction that = (IndexAction) o;
+
+ if (index != null ? !index.equals(that.index) : that.index != null) return false;
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = index != null ? index.hashCode() : 0;
+ result = 31 * result + (type != null ? type.hashCode() : 0);
+ return result;
+ }
+
public static class Parser extends AbstractComponent implements Action.Parser {
public static final ParseField INDEX_FIELD = new ParseField("index");
@@ -127,19 +148,19 @@ public class IndexAction extends Action {
} else if (TYPE_FIELD.match(currentFieldName)) {
type = parser.text();
} else {
- throw new ActionException("could not parse index action. unexpected field [" + currentFieldName + "]");
+ throw new ActionSettingsException("could not parse index action. unexpected field [" + currentFieldName + "]");
}
} else {
- throw new ActionException("could not parse index action. unexpected token [" + token + "]");
+ throw new ActionSettingsException("could not parse index action. unexpected token [" + token + "]");
}
}
if (index == null) {
- throw new ActionException("could not parse index action [index] is required");
+ throw new ActionSettingsException("could not parse index action [index] is required");
}
if (type == null) {
- throw new ActionException("could not parse index action [type] is required");
+ throw new ActionSettingsException("could not parse index action [type] is required");
}
return new IndexAction(logger, client, index, type);
@@ -215,24 +236,4 @@ public class IndexAction extends Action {
}
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- IndexAction that = (IndexAction) o;
-
- if (index != null ? !index.equals(that.index) : that.index != null) return false;
- if (type != null ? !type.equals(that.type) : that.type != null) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = index != null ? index.hashCode() : 0;
- result = 31 * result + (type != null ? type.hashCode() : 0);
- return result;
- }
}
diff --git a/src/main/java/org/elasticsearch/alerts/actions/webhook/WebhookAction.java b/src/main/java/org/elasticsearch/alerts/actions/webhook/WebhookAction.java
index 1f72d18ac85..885a474b728 100644
--- a/src/main/java/org/elasticsearch/alerts/actions/webhook/WebhookAction.java
+++ b/src/main/java/org/elasticsearch/alerts/actions/webhook/WebhookAction.java
@@ -5,13 +5,17 @@
*/
package org.elasticsearch.alerts.actions.webhook;
+import org.elasticsearch.alerts.AlertsSettingsException;
import org.elasticsearch.alerts.ExecutionContext;
import org.elasticsearch.alerts.Payload;
import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.ActionException;
-import org.elasticsearch.alerts.support.StringTemplateUtils;
+import org.elasticsearch.alerts.actions.ActionSettingsException;
+import org.elasticsearch.alerts.support.template.Template;
+import org.elasticsearch.alerts.support.template.XContentTemplate;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
@@ -21,8 +25,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
/**
*/
@@ -30,27 +32,18 @@ public class WebhookAction extends Action {
public static final String TYPE = "webhook";
- static final StringTemplateUtils.Template DEFAULT_BODY_TEMPLATE = new StringTemplateUtils.Template(
- "{ 'alertname' : '{{alert_name}}', 'response' : {{response}} }");
-
- private final StringTemplateUtils templateUtils;
private final HttpClient httpClient;
- private final StringTemplateUtils.Template urlTemplate;
private final HttpMethod method;
+ private final Template url;
+ private final @Nullable Template body;
- //Optional, default will be used if not provided
- private final StringTemplateUtils.Template bodyTemplate;
-
- public WebhookAction(ESLogger logger, StringTemplateUtils templateUtils, HttpClient httpClient,
- @Nullable StringTemplateUtils.Template bodyTemplate,
- StringTemplateUtils.Template urlTemplate, HttpMethod method) {
+ public WebhookAction(ESLogger logger, HttpClient httpClient, HttpMethod method, Template url, Template body) {
super(logger);
- this.templateUtils = templateUtils;
this.httpClient = httpClient;
- this.bodyTemplate = bodyTemplate;
- this.urlTemplate = urlTemplate;
this.method = method;
+ this.url = url;
+ this.body = body;
}
@Override
@@ -60,44 +53,62 @@ public class WebhookAction extends Action {
@Override
public Result execute(ExecutionContext ctx, Payload payload) throws IOException {
- Map data = payload.data();
- String renderedUrl = applyTemplate(templateUtils, urlTemplate, ctx.alert().name(), data);
- String body = applyTemplate(templateUtils, bodyTemplate != null ? bodyTemplate : DEFAULT_BODY_TEMPLATE, ctx.alert().name(), data);
+ ImmutableMap model = ImmutableMap.builder()
+ .put(ALERT_NAME_VARIABLE, ctx.alert().name())
+ .put(PAYLOAD_VARIABLE, payload.data())
+ .build();
+ String urlText = url.render(model);
+ String bodyText = body != null ? body.render(model) : XContentTemplate.YAML.render(model);
try {
- int status = httpClient.execute(method, renderedUrl, body);
+ int status = httpClient.execute(method, urlText, bodyText);
if (status >= 400) {
- logger.warn("got status [" + status + "] when connecting to [" + renderedUrl + "]");
+ logger.warn("got status [" + status + "] when connecting to [" + urlText + "]");
} else {
if (status >= 300) {
logger.warn("a 200 range return code was expected, but got [" + status + "]");
}
}
- return new Result.Executed(status, renderedUrl, body);
+ return new Result.Executed(status, urlText, bodyText);
+
} catch (IOException ioe) {
- logger.error("failed to connect to [{}] for alert [{}]", ioe, renderedUrl, ctx.alert().name());
+ logger.error("failed to connect to [{}] for alert [{}]", ioe, urlText, ctx.alert().name());
return new Result.Failure("failed to send http request. " + ioe.getMessage());
}
}
- static String applyTemplate(StringTemplateUtils templateUtils, StringTemplateUtils.Template template, String alertName, Map data) {
- Map webHookParams = new HashMap<>();
- webHookParams.put(ALERT_NAME_VARIABLE_NAME, alertName);
- webHookParams.put(RESPONSE_VARIABLE_NAME, data);
- return templateUtils.executeTemplate(template, webHookParams);
- }
-
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Parser.METHOD_FIELD.getPreferredName(), method.getName());
- if (bodyTemplate != null) {
- StringTemplateUtils.writeTemplate(Parser.BODY_TEMPLATE_FIELD.getPreferredName(), bodyTemplate, builder, params);
+ builder.field(Parser.URL_FIELD.getPreferredName(), url);
+ if (body != null) {
+ builder.field(Parser.BODY_FIELD.getPreferredName(), body);
}
- StringTemplateUtils.writeTemplate(Parser.URL_TEMPLATE_FIELD.getPreferredName(), urlTemplate, builder, params);
- builder.endObject();
- return builder;
+ return builder.endObject();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ WebhookAction that = (WebhookAction) o;
+
+ if (!body.equals(that.body)) return false;
+ if (!method.equals(that.method)) return false;
+ if (!url.equals(that.url)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = method.hashCode();
+ result = 31 * result + url.hashCode();
+ result = 31 * result + body.hashCode();
+ return result;
}
public abstract static class Result extends Action.Result {
@@ -108,19 +119,19 @@ public class WebhookAction extends Action {
public static class Executed extends Result {
- private final int httpStatusCode;
+ private final int httpStatus;
private final String url;
private final String body;
- public Executed(int httpStatusCode, String url, String body) {
- super(TYPE, httpStatusCode < 400);
- this.httpStatusCode = httpStatusCode;
+ public Executed(int httpStatus, String url, String body) {
+ super(TYPE, httpStatus < 400);
+ this.httpStatus = httpStatus;
this.url = url;
this.body = body;
}
- public int httpStatusCode() {
- return httpStatusCode;
+ public int httpStatus() {
+ return httpStatus;
}
public String url() {
@@ -134,9 +145,9 @@ public class WebhookAction extends Action {
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field("success", success())
- .field(Parser.HTTP_STATUS_FIELD.getPreferredName(), httpStatusCode)
- .field(Parser.URL_FIELD.getPreferredName(), url)
- .field(Parser.BODY_FIELD.getPreferredName(), body);
+ .field(WebhookAction.Parser.HTTP_STATUS_FIELD.getPreferredName(), httpStatus)
+ .field(WebhookAction.Parser.URL_FIELD.getPreferredName(), url)
+ .field(WebhookAction.Parser.BODY_FIELD.getPreferredName(), body);
}
}
@@ -151,7 +162,7 @@ public class WebhookAction extends Action {
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
- return builder.field(Parser.REASON_FIELD.getPreferredName(), reason);
+ return builder.field(WebhookAction.Parser.REASON_FIELD.getPreferredName(), reason);
}
}
}
@@ -160,21 +171,18 @@ public class WebhookAction extends Action {
public static class Parser extends AbstractComponent implements Action.Parser {
public static final ParseField METHOD_FIELD = new ParseField("method");
- public static final ParseField URL_TEMPLATE_FIELD = new ParseField("url_template");
- public static final ParseField BODY_TEMPLATE_FIELD = new ParseField("body_template");
-
- public static final ParseField BODY_FIELD = new ParseField("body");
public static final ParseField URL_FIELD = new ParseField("url");
+ public static final ParseField BODY_FIELD = new ParseField("body");
public static final ParseField HTTP_STATUS_FIELD = new ParseField("http_status");
public static final ParseField REASON_FIELD = new ParseField("reason");
- private final StringTemplateUtils templateUtils;
+ private final Template.Parser templateParser;
private final HttpClient httpClient;
@Inject
- public Parser(Settings settings, StringTemplateUtils templateUtils, HttpClient httpClient) {
+ public Parser(Settings settings, Template.Parser templateParser, HttpClient httpClient) {
super(settings);
- this.templateUtils = templateUtils;
+ this.templateParser = templateParser;
this.httpClient = httpClient;
}
@@ -186,8 +194,8 @@ public class WebhookAction extends Action {
@Override
public WebhookAction parse(XContentParser parser) throws IOException {
HttpMethod method = HttpMethod.POST;
- StringTemplateUtils.Template urlTemplate = null;
- StringTemplateUtils.Template bodyTemplate = null;
+ Template urlTemplate = null;
+ Template bodyTemplate = null;
String currentFieldName = null;
XContentParser.Token token;
@@ -199,26 +207,33 @@ public class WebhookAction extends Action {
if (METHOD_FIELD.match(currentFieldName)) {
method = HttpMethod.valueOf(parser.text());
if (method != HttpMethod.POST && method != HttpMethod.GET && method != HttpMethod.PUT) {
- throw new ActionException("could not parse webhook action. unsupported http method ["
- + method.getName() + "]");
+ throw new ActionSettingsException("could not parse webhook action. unsupported http method [" + method.getName() + "]");
+ }
+ } else if (URL_FIELD.match(currentFieldName)) {
+ try {
+ urlTemplate = templateParser.parse(parser);
+ } catch (Template.Parser.ParseException pe) {
+ throw new AlertsSettingsException("could not parse webhook action [url] template", pe);
+ }
+ } else if (BODY_FIELD.match(currentFieldName)) {
+ try {
+ bodyTemplate = templateParser.parse(parser);
+ } catch (Template.Parser.ParseException pe) {
+ throw new ActionSettingsException("could not parse webhook action [body] template", pe);
}
- } else if (URL_TEMPLATE_FIELD.match(currentFieldName)) {
- urlTemplate = StringTemplateUtils.readTemplate(parser);
- } else if (BODY_TEMPLATE_FIELD.match(currentFieldName)) {
- bodyTemplate = StringTemplateUtils.readTemplate(parser);
} else {
- throw new ActionException("could not parse webhook action. unexpected field [" + currentFieldName + "]");
+ throw new ActionSettingsException("could not parse webhook action. unexpected field [" + currentFieldName + "]");
}
} else {
- throw new ActionException("could not parse webhook action. unexpected token [" + token + "]");
+ throw new ActionSettingsException("could not parse webhook action. unexpected token [" + token + "]");
}
}
if (urlTemplate == null) {
- throw new ActionException("could not parse webhook action. [url_template] is required");
+ throw new ActionSettingsException("could not parse webhook action. [url_template] is required");
}
- return new WebhookAction(logger, templateUtils, httpClient, bodyTemplate, urlTemplate, method);
+ return new WebhookAction(logger, httpClient, method, urlTemplate, bodyTemplate);
}
@Override
@@ -264,25 +279,4 @@ public class WebhookAction extends Action {
}
}
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- WebhookAction that = (WebhookAction) o;
-
- if (bodyTemplate != null ? !bodyTemplate.equals(that.bodyTemplate) : that.bodyTemplate != null) return false;
- if (method != null ? !method.equals(that.method) : that.method != null) return false;
- if (urlTemplate != null ? !urlTemplate.equals(that.urlTemplate) : that.urlTemplate != null) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = urlTemplate != null ? urlTemplate.hashCode() : 0;
- result = 31 * result + (method != null ? method.hashCode() : 0);
- result = 31 * result + (bodyTemplate != null ? bodyTemplate.hashCode() : 0);
- return result;
- }
}
diff --git a/src/main/java/org/elasticsearch/alerts/support/AlertUtils.java b/src/main/java/org/elasticsearch/alerts/support/AlertUtils.java
index 5011cd87b10..39ae9e1e16b 100644
--- a/src/main/java/org/elasticsearch/alerts/support/AlertUtils.java
+++ b/src/main/java/org/elasticsearch/alerts/support/AlertUtils.java
@@ -134,7 +134,7 @@ public final class AlertUtils {
searchRequest.templateName(parser.textOrNull());
break;
case "template_type":
- searchRequest.templateType(readScriptType(parser.textOrNull()));
+ searchRequest.templateType(ScriptService.ScriptType.valueOf(parser.text().toUpperCase(Locale.ROOT)));
break;
case "search_type":
searchType = SearchType.fromString(parser.text());
@@ -169,7 +169,7 @@ public final class AlertUtils {
builder.field("template_name", searchRequest.templateName());
}
if (searchRequest.templateType() != null) {
- builder.field("template_type", writeScriptType(searchRequest.templateType()));
+ builder.field("template_type", searchRequest.templateType().name().toLowerCase(Locale.ROOT));
}
builder.startArray("indices");
for (String index : searchRequest.indices()) {
@@ -200,30 +200,4 @@ public final class AlertUtils {
builder.endObject();
}
- static ScriptService.ScriptType readScriptType(String value) {
- switch (value) {
- case "indexed":
- return ScriptService.ScriptType.INDEXED;
- case "inline":
- return ScriptService.ScriptType.INLINE;
- case "file":
- return ScriptService.ScriptType.FILE;
- default:
- throw new ElasticsearchIllegalArgumentException("Unknown script_type value [" + value + "]");
- }
- }
-
- static String writeScriptType(ScriptService.ScriptType value) {
- switch (value) {
- case INDEXED:
- return "indexed";
- case INLINE:
- return "inline";
- case FILE:
- return "file";
- default:
- throw new ElasticsearchIllegalArgumentException("Illegal script_type value [" + value + "]");
- }
- }
-
}
diff --git a/src/main/java/org/elasticsearch/alerts/support/StringTemplateUtils.java b/src/main/java/org/elasticsearch/alerts/support/StringTemplateUtils.java
deleted file mode 100644
index 6c9292cdcc1..00000000000
--- a/src/main/java/org/elasticsearch/alerts/support/StringTemplateUtils.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.ElasticsearchIllegalArgumentException;
-import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
-import org.elasticsearch.common.bytes.BytesReference;
-import org.elasticsearch.common.component.AbstractComponent;
-import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.ToXContent;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.script.ExecutableScript;
-import org.elasticsearch.script.ScriptService;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- */
-public class StringTemplateUtils extends AbstractComponent {
-
- private final ScriptServiceProxy scriptService;
-
- @Inject
- public StringTemplateUtils(Settings settings, ScriptServiceProxy scriptService) {
- super(settings);
- this.scriptService = scriptService;
- }
-
- public String executeTemplate(Template template) {
- return executeTemplate(template, Collections.emptyMap());
- }
-
- public String executeTemplate(Template template, Map additionalParams) {
- Map params = new HashMap<>();
- params.putAll(template.getParams());
- params.putAll(additionalParams);
- ExecutableScript script = scriptService.executable(template.getLanguage(), template.getTemplate(), template.getScriptType(), params);
- Object result = script.run();
- if (result instanceof String) {
- return (String) result;
- } else if (result instanceof BytesReference) {
- return ((BytesReference) script.run()).toUtf8();
- } else {
- return result.toString();
- }
- }
- public static Template readTemplate(XContentParser parser) throws IOException {
- assert parser.currentToken() == XContentParser.Token.START_OBJECT : "Expected START_OBJECT, but was " + parser.currentToken();
- Map params = null;
- String script = null;
- ScriptService.ScriptType type = ScriptService.ScriptType.INLINE;
- String language = "mustache";
- String fieldName = parser.currentName();
- for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) {
- switch (token) {
- case FIELD_NAME:
- fieldName = parser.currentName();
- break;
- case START_OBJECT:
- switch (fieldName) {
- case "params":
- params = (Map) parser.map();
- break;
- default:
- throw new ElasticsearchIllegalArgumentException("Unexpected field [" + fieldName + "]");
- }
- break;
- case VALUE_STRING:
- switch (fieldName) {
- case "script":
- script = parser.text();
- break;
- case "language":
- language = parser.text();
- break;
- case "type":
- type = AlertUtils.readScriptType(parser.text());
- break;
- default:
- throw new ElasticsearchIllegalArgumentException("Unexpected field [" + fieldName + "]");
- }
- break;
- default:
- throw new ElasticsearchIllegalArgumentException("Unexpected json token [" + token + "]");
- }
- }
- return new Template(script, params, language, type);
- }
-
- public static void writeTemplate(String objectName, Template template, XContentBuilder builder, ToXContent.Params params) throws IOException {
- builder.startObject(objectName);
- builder.field("script", template.getTemplate());
- builder.field("type", AlertUtils.writeScriptType(template.getScriptType()));
- builder.field("language", template.getLanguage());
- if (template.getParams() != null && !template.getParams().isEmpty()) {
- builder.field("params", template.getParams());
- }
- builder.endObject();
- }
-
- public static class Template {
- private final String template;
- private final Map params;
- private final String language;
- private final ScriptService.ScriptType scriptType;
- public Template(String template) {
- this.template = template;
- this.params = Collections.emptyMap();
- this.language = "mustache";
- this.scriptType = ScriptService.ScriptType.INLINE;
- }
- public Template(String template, Map params, String language, ScriptService.ScriptType scriptType) {
- this.template = template;
- this.params = params;
- this.language = language;
- this.scriptType = scriptType;
- }
- public ScriptService.ScriptType getScriptType() {
- return scriptType;
- }
- public String getTemplate() {
- return template;
- }
- public String getLanguage() {
- return language;
- }
- public Map getParams() {
- return params;
- }
-
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Template template1 = (Template) o;
-
- if (language != null ? !language.equals(template1.language) : template1.language != null) return false;
- if (params != null ? !params.equals(template1.params) : template1.params != null) return false;
- if (scriptType != template1.scriptType) return false;
- if (template != null ? !template.equals(template1.template) : template1.template != null) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = template != null ? template.hashCode() : 0;
- result = 31 * result + (params != null ? params.hashCode() : 0);
- result = 31 * result + (language != null ? language.hashCode() : 0);
- result = 31 * result + (scriptType != null ? scriptType.hashCode() : 0);
- return result;
- }
- }
-}
-
diff --git a/src/main/java/org/elasticsearch/alerts/support/template/ScriptTemplate.java b/src/main/java/org/elasticsearch/alerts/support/template/ScriptTemplate.java
new file mode 100644
index 00000000000..029bdc1a81f
--- /dev/null
+++ b/src/main/java/org/elasticsearch/alerts/support/template/ScriptTemplate.java
@@ -0,0 +1,193 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.base.Function;
+import org.elasticsearch.common.base.Joiner;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.collect.Collections2;
+import org.elasticsearch.common.collect.ImmutableList;
+import org.elasticsearch.common.collect.ImmutableMap;
+import org.elasticsearch.common.component.AbstractComponent;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.script.ExecutableScript;
+import org.elasticsearch.script.ScriptService;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ *
+ */
+public class ScriptTemplate implements ToXContent, Template {
+
+ static final ParseField TEXT_FIELD = new ParseField("script", "text");
+ static final ParseField LANG_FIELD = new ParseField("lang", "language", "script_lang");
+ static final ParseField TYPE_FIELD = new ParseField("type", "script_type");
+ static final ParseField PARAMS_FIELD = new ParseField("model", "params");
+
+ public static final String DEFAULT_LANG = "mustache";
+
+ private final String text;
+ private final String lang;
+ private final ScriptService.ScriptType type;
+ private final Map params;
+ private final ScriptServiceProxy service;
+
+ public ScriptTemplate(ScriptServiceProxy service, String text) {
+ this(service, text, DEFAULT_LANG, ScriptService.ScriptType.INLINE, Collections.emptyMap());
+ }
+
+ public ScriptTemplate(ScriptServiceProxy service, String text, String lang, ScriptService.ScriptType type, Map params) {
+ this.service = service;
+ this.text = text;
+ this.lang = lang;
+ this.type = type;
+ this.params = params;
+ }
+
+ public String text() {
+ return text;
+ }
+
+ public ScriptService.ScriptType type() {
+ return type;
+ }
+
+ public String lang() {
+ return lang;
+ }
+
+ public Map params() {
+ return params;
+ }
+
+ @Override
+ public String render(Map model) {
+ Map mergedModel = new HashMap<>();
+ mergedModel.putAll(params);
+ mergedModel.putAll(model);
+ ExecutableScript script = service.executable(lang, text, type, mergedModel);
+ Object result = script.run();
+ if (result instanceof BytesReference) {
+ return ((BytesReference) script.run()).toUtf8();
+ }
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ScriptTemplate template = (ScriptTemplate) o;
+
+ if (!lang.equals(template.lang)) return false;
+ if (!params.equals(template.params)) return false;
+ if (!text.equals(template.text)) return false;
+ if (type != template.type) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = text.hashCode();
+ result = 31 * result + lang.hashCode();
+ result = 31 * result + type.hashCode();
+ result = 31 * result + params.hashCode();
+ return result;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ return builder.startObject()
+ .field(TEXT_FIELD.getPreferredName(), text)
+ .field(TYPE_FIELD.getPreferredName(), type.name().toLowerCase(Locale.ROOT))
+ .field(LANG_FIELD.getPreferredName(), lang)
+ .field(PARAMS_FIELD.getPreferredName(), this.params)
+ .endObject();
+ }
+
+ /**
+ */
+ public static class Parser extends AbstractComponent implements Template.Parser {
+
+ private final ScriptServiceProxy scriptService;
+
+ @Inject
+ public Parser(Settings settings, ScriptServiceProxy scriptService) {
+ super(settings);
+ this.scriptService = scriptService;
+ }
+
+ @Override
+ public ScriptTemplate parse(XContentParser parser) throws IOException {
+ assert parser.currentToken() == XContentParser.Token.START_OBJECT : "Expected START_OBJECT, but was " + parser.currentToken();
+
+ String text = null;
+ ScriptService.ScriptType type = ScriptService.ScriptType.INLINE;
+ String lang = DEFAULT_LANG;
+ ImmutableMap.Builder params = ImmutableMap.builder();
+
+ String currentFieldName = null;
+ XContentParser.Token token;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (TEXT_FIELD.match(currentFieldName)) {
+ if (token.isValue()) {
+ text = parser.text();
+ } else {
+ throw new ParseException("expected a string value for [" + currentFieldName + "], but found [" + token + "] instead");
+ }
+ } else if (LANG_FIELD.match(currentFieldName)) {
+ if (token == XContentParser.Token.VALUE_STRING) {
+ lang = parser.text();
+ } else {
+ throw new ParseException("expected a string value for [" + currentFieldName + "], but found [" + token + "] instead");
+ }
+ } 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) {
+ String typeOptions = Joiner.on(",").join(Collections2.transform(ImmutableList.copyOf(ScriptService.ScriptType.values()), new Function() {
+ @Override
+ public String apply(ScriptService.ScriptType scriptType) {
+ return scriptType.name().toLowerCase(Locale.ROOT);
+ }
+ }));
+ throw new ParseException("unknown template script type [" + currentFieldName + "]. type can only be on of: [" + typeOptions + "]");
+ }
+ }
+ } else if (PARAMS_FIELD.match(currentFieldName)) {
+ if (token != XContentParser.Token.START_OBJECT) {
+ throw new ParseException("expected an object for [" + currentFieldName + "], but found [" + token + "]");
+ }
+ params.putAll(parser.map());
+ } else {
+ throw new ParseException("unexpected field [" + currentFieldName + "]");
+ }
+ }
+ if (text == null) {
+ throw new ParseException("missing required field [" + TEXT_FIELD.getPreferredName() + "]");
+ }
+ return new ScriptTemplate(scriptService, text, lang, type, params.build());
+ }
+ }
+
+}
diff --git a/src/main/java/org/elasticsearch/alerts/support/template/Template.java b/src/main/java/org/elasticsearch/alerts/support/template/Template.java
new file mode 100644
index 00000000000..b60bd317988
--- /dev/null
+++ b/src/main/java/org/elasticsearch/alerts/support/template/Template.java
@@ -0,0 +1,34 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.alerts.AlertsException;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ *
+ */
+public interface Template extends ToXContent {
+
+ String render(Map model);
+
+ interface Parser {
+
+ T parse(XContentParser parser) throws IOException, ParseException;
+
+ public static class ParseException extends AlertsException {
+
+ public ParseException(String msg) {
+ super(msg);
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/org/elasticsearch/alerts/support/template/TemplateException.java b/src/main/java/org/elasticsearch/alerts/support/template/TemplateException.java
new file mode 100644
index 00000000000..803ca634a6b
--- /dev/null
+++ b/src/main/java/org/elasticsearch/alerts/support/template/TemplateException.java
@@ -0,0 +1,22 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.alerts.AlertsException;
+
+/**
+ *
+ */
+public class TemplateException extends AlertsException {
+
+ public TemplateException(String msg) {
+ super(msg);
+ }
+
+ public TemplateException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/src/main/java/org/elasticsearch/alerts/support/template/TemplateModule.java b/src/main/java/org/elasticsearch/alerts/support/template/TemplateModule.java
new file mode 100644
index 00000000000..0e906f30127
--- /dev/null
+++ b/src/main/java/org/elasticsearch/alerts/support/template/TemplateModule.java
@@ -0,0 +1,19 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.common.inject.AbstractModule;
+
+/**
+ *
+ */
+public class TemplateModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(Template.Parser.class).to(ScriptTemplate.Parser.class).asEagerSingleton();
+ }
+}
diff --git a/src/main/java/org/elasticsearch/alerts/support/template/XContentTemplate.java b/src/main/java/org/elasticsearch/alerts/support/template/XContentTemplate.java
new file mode 100644
index 00000000000..96474e20292
--- /dev/null
+++ b/src/main/java/org/elasticsearch/alerts/support/template/XContentTemplate.java
@@ -0,0 +1,42 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.common.xcontent.XContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ *
+ */
+public class XContentTemplate implements Template {
+
+ public static XContentTemplate YAML = new XContentTemplate(YamlXContent.yamlXContent);
+
+ private final XContent xContent;
+
+ private XContentTemplate(XContent xContent) {
+ this.xContent = xContent;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ return builder.startObject().endObject();
+ }
+
+ @Override
+ public String render(Map model) {
+ try {
+ return XContentBuilder.builder(xContent).map(model).bytes().toUtf8();
+ } catch (IOException ioe) {
+ throw new TemplateException("could not render [" + xContent.type().name() + "] xcontent template", ioe);
+ }
+ }
+
+}
diff --git a/src/test/java/org/elasticsearch/alerts/AbstractAlertingTests.java b/src/test/java/org/elasticsearch/alerts/AbstractAlertingTests.java
index 6e65ef916e9..6315fed66ac 100644
--- a/src/test/java/org/elasticsearch/alerts/AbstractAlertingTests.java
+++ b/src/test/java/org/elasticsearch/alerts/AbstractAlertingTests.java
@@ -24,9 +24,10 @@ import org.elasticsearch.alerts.history.FiredAlert;
import org.elasticsearch.alerts.history.HistoryStore;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.AlertUtils;
-import org.elasticsearch.alerts.support.StringTemplateUtils;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
+import org.elasticsearch.alerts.support.template.ScriptTemplate;
+import org.elasticsearch.alerts.support.template.Template;
import org.elasticsearch.alerts.transform.SearchTransform;
import org.elasticsearch.alerts.transport.actions.stats.AlertsStatsResponse;
import org.elasticsearch.client.Client;
@@ -162,10 +163,10 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
List actions = new ArrayList<>();
- StringTemplateUtils.Template template =
- new StringTemplateUtils.Template("{{alert_name}} executed with {{response.hits.total}} hits");
+ Template url = new ScriptTemplate(scriptService(), "http://localhost/foobarbaz/{{alert_name}}");
+ Template body = new ScriptTemplate(scriptService(), "{{alert_name}} executed with {{response.hits.total}} hits");
- actions.add(new WebhookAction(logger, stringTemplateUtils(), httpClient(), template, new StringTemplateUtils.Template("http://localhost/foobarbaz/{{alert_name}}"), HttpMethod.GET));
+ actions.add(new WebhookAction(logger, httpClient(), HttpMethod.GET, url, body));
Email.Address from = new Email.Address("from@test.com");
List emailAddressList = new ArrayList<>();
@@ -178,8 +179,8 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
emailBuilder.to(to);
- EmailAction emailAction = new EmailAction(logger, noopEmailService(), stringTemplateUtils(), emailBuilder,
- new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", template, template, null, true);
+ EmailAction emailAction = new EmailAction(logger, noopEmailService(), emailBuilder,
+ new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", body, body, null, true);
actions.add(emailAction);
@@ -189,9 +190,9 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
return new Alert(
alertName,
new CronSchedule("0/5 * * * * ? *"),
- new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
+ new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
conditionRequest,"return true", ScriptService.ScriptType.INLINE, "groovy"),
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), transformRequest),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest),
new TimeValue(0),
new Actions(actions),
metadata,
@@ -204,8 +205,12 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
return internalTestCluster().getInstance(AlertsClient.class);
}
- protected ScriptService scriptService() {
- return internalTestCluster().getInstance(ScriptService.class);
+ protected ScriptServiceProxy scriptService() {
+ return internalTestCluster().getInstance(ScriptServiceProxy.class);
+ }
+
+ protected Template.Parser templateParser() {
+ return internalTestCluster().getInstance(Template.Parser.class);
}
protected HttpClient httpClient() {
@@ -216,10 +221,6 @@ public abstract class AbstractAlertingTests extends ElasticsearchIntegrationTest
return new NoopEmailService();
}
- protected StringTemplateUtils stringTemplateUtils() {
- return internalTestCluster().getInstance(StringTemplateUtils.class);
- }
-
protected FiredAlert.Parser firedAlertParser() {
return internalTestCluster().getInstance(FiredAlert.Parser.class);
}
diff --git a/src/test/java/org/elasticsearch/alerts/AlertThrottleTests.java b/src/test/java/org/elasticsearch/alerts/AlertThrottleTests.java
index ebdaf715668..d573baeea3c 100644
--- a/src/test/java/org/elasticsearch/alerts/AlertThrottleTests.java
+++ b/src/test/java/org/elasticsearch/alerts/AlertThrottleTests.java
@@ -15,16 +15,15 @@ import org.elasticsearch.alerts.actions.Action;
import org.elasticsearch.alerts.actions.Actions;
import org.elasticsearch.alerts.actions.index.IndexAction;
import org.elasticsearch.alerts.client.AlertsClient;
+import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.history.FiredAlert;
import org.elasticsearch.alerts.history.HistoryStore;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
-import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
import org.elasticsearch.alerts.transport.actions.ack.AckAlertResponse;
import org.elasticsearch.alerts.transport.actions.get.GetAlertResponse;
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
-import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -40,9 +39,7 @@ import java.util.concurrent.TimeUnit;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.IsEqual.equalTo;
/**
@@ -69,9 +66,9 @@ public class AlertThrottleTests extends AbstractAlertingTests {
Alert alert = new Alert(
"test-serialization",
new CronSchedule("0/5 * * * * ? *"),
- new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
+ new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
request, "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), request),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request),
new TimeValue(0),
new Actions(actions),
null,
@@ -152,9 +149,9 @@ public class AlertThrottleTests extends AbstractAlertingTests {
Alert alert = new Alert(
"test-time-throttle",
new CronSchedule("0/5 * * * * ? *"),
- new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
+ new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
request, "hits.total > 0", ScriptService.ScriptType.INLINE, "groovy"),
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), request),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), request),
new TimeValue(10, TimeUnit.SECONDS),
new Actions(actions),
null,
diff --git a/src/test/java/org/elasticsearch/alerts/BootStrapTest.java b/src/test/java/org/elasticsearch/alerts/BootStrapTest.java
index da2dc5c802f..82038a6321c 100644
--- a/src/test/java/org/elasticsearch/alerts/BootStrapTest.java
+++ b/src/test/java/org/elasticsearch/alerts/BootStrapTest.java
@@ -16,7 +16,6 @@ import org.elasticsearch.alerts.history.FiredAlert;
import org.elasticsearch.alerts.history.HistoryStore;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
-import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
import org.elasticsearch.alerts.transport.actions.stats.AlertsStatsResponse;
@@ -80,9 +79,9 @@ public class BootStrapTest extends AbstractAlertingTests {
Alert alert = new Alert(
"test-serialization",
new CronSchedule("0/5 * * * * ? 2035"),
- new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
+ new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
searchRequest, "return true", ScriptService.ScriptType.INLINE, "groovy"),
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), searchRequest),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), searchRequest),
new TimeValue(0),
new Actions(new ArrayList()),
null,
@@ -140,9 +139,9 @@ public class BootStrapTest extends AbstractAlertingTests {
Alert alert = new Alert(
"action-test-"+ i + " " + j,
new CronSchedule("0/5 * * * * ? 2035"), //Set a cron schedule far into the future so this alert is never scheduled
- new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
+ new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
searchRequest, "return true", ScriptService.ScriptType.INLINE, "groovy"),
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), searchRequest),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), searchRequest),
new TimeValue(0),
new Actions(new ArrayList()),
null,
diff --git a/src/test/java/org/elasticsearch/alerts/TransformSearchTest.java b/src/test/java/org/elasticsearch/alerts/TransformSearchTest.java
index 1dbb7ef9ea5..364082e712d 100644
--- a/src/test/java/org/elasticsearch/alerts/TransformSearchTest.java
+++ b/src/test/java/org/elasticsearch/alerts/TransformSearchTest.java
@@ -13,7 +13,6 @@ import org.elasticsearch.alerts.actions.index.IndexAction;
import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
-import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
import org.elasticsearch.common.unit.TimeValue;
@@ -59,9 +58,9 @@ public class TransformSearchTest extends AbstractAlertingTests {
Alert alert = new Alert(
"test-serialization",
new CronSchedule("0/5 * * * * ? *"),
- new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()),
+ new ScriptSearchCondition(logger, scriptService(), ClientProxy.of(client()),
conditionRequest,"return true", ScriptService.ScriptType.INLINE, "groovy"),
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), transformRequest),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), transformRequest),
new TimeValue(0),
new Actions(actions),
metadata,
diff --git a/src/test/java/org/elasticsearch/alerts/actions/ActionsTest.java b/src/test/java/org/elasticsearch/alerts/actions/ActionsTest.java
index 283ae53655f..5446e4b8a32 100644
--- a/src/test/java/org/elasticsearch/alerts/actions/ActionsTest.java
+++ b/src/test/java/org/elasticsearch/alerts/actions/ActionsTest.java
@@ -9,9 +9,10 @@ package org.elasticsearch.alerts.actions;
import org.elasticsearch.alerts.AbstractAlertingTests;
import org.elasticsearch.alerts.Alert;
import org.elasticsearch.alerts.actions.index.IndexAction;
+import org.elasticsearch.alerts.condition.Condition;
+import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
import org.elasticsearch.alerts.support.init.proxy.ClientProxy;
-import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.alerts.transform.SearchTransform;
import org.elasticsearch.alerts.transport.actions.delete.DeleteAlertRequest;
import org.elasticsearch.alerts.transport.actions.delete.DeleteAlertResponse;
@@ -19,8 +20,6 @@ import org.elasticsearch.alerts.transport.actions.get.GetAlertRequest;
import org.elasticsearch.alerts.transport.actions.get.GetAlertResponse;
import org.elasticsearch.alerts.transport.actions.put.PutAlertRequest;
import org.elasticsearch.alerts.transport.actions.put.PutAlertResponse;
-import org.elasticsearch.alerts.condition.Condition;
-import org.elasticsearch.alerts.condition.search.ScriptSearchCondition;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -57,7 +56,7 @@ public class ActionsTest extends AbstractAlertingTests {
final List actionList = new ArrayList<>();
actionList.add(alertAction);
- Condition alertCondition = new ScriptSearchCondition(logger, ScriptServiceProxy.of(scriptService()),
+ Condition alertCondition = new ScriptSearchCondition(logger, scriptService(),
ClientProxy.of(client()), createConditionSearchRequest(), "return true", ScriptService.ScriptType.INLINE, "groovy");
@@ -65,7 +64,7 @@ public class ActionsTest extends AbstractAlertingTests {
"my-first-alert",
new CronSchedule("0/5 * * * * ? *"),
alertCondition,
- new SearchTransform(logger, ScriptServiceProxy.of(scriptService()), ClientProxy.of(client()), createConditionSearchRequest()),
+ new SearchTransform(logger, scriptService(), ClientProxy.of(client()), createConditionSearchRequest()),
new TimeValue(0),
new Actions(actionList),
null,
diff --git a/src/test/java/org/elasticsearch/alerts/actions/email/EmailActionTest.java b/src/test/java/org/elasticsearch/alerts/actions/email/EmailActionTest.java
index 3bd28662398..38d29972222 100644
--- a/src/test/java/org/elasticsearch/alerts/actions/email/EmailActionTest.java
+++ b/src/test/java/org/elasticsearch/alerts/actions/email/EmailActionTest.java
@@ -13,8 +13,8 @@ import org.elasticsearch.alerts.actions.email.service.Email;
import org.elasticsearch.alerts.actions.email.service.EmailService;
import org.elasticsearch.alerts.actions.email.service.Profile;
import org.elasticsearch.alerts.scheduler.schedule.CronSchedule;
-import org.elasticsearch.alerts.support.StringTemplateUtils;
import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
+import org.elasticsearch.alerts.support.template.ScriptTemplate;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
@@ -40,8 +40,6 @@ import java.util.Set;
public class EmailActionTest extends ElasticsearchTestCase {
public void testEmailTemplateRender() throws IOException, MessagingException {
- StringTemplateUtils.Template template =
- new StringTemplateUtils.Template("{{alert_name}} executed with {{response.hits.total}} hits");
Settings settings = ImmutableSettings.settingsBuilder().build();
MustacheScriptEngineService mustacheScriptEngineService = new MustacheScriptEngineService(settings);
@@ -49,8 +47,9 @@ public class EmailActionTest extends ElasticsearchTestCase {
Set engineServiceSet = new HashSet<>();
engineServiceSet.add(mustacheScriptEngineService);
- ScriptService scriptService = new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, threadPool));
- StringTemplateUtils stringTemplateUtils = new StringTemplateUtils(settings, ScriptServiceProxy.of(scriptService));
+ ScriptServiceProxy scriptService = ScriptServiceProxy.of(new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, threadPool)));
+
+ ScriptTemplate template = new ScriptTemplate(scriptService, "{{alert_name}} executed with {{response.hits.total}} hits");
EmailService emailService = new EmailServiceMock();
@@ -64,7 +63,7 @@ public class EmailActionTest extends ElasticsearchTestCase {
emailBuilder.from(from);
emailBuilder.to(to);
- EmailAction emailAction = new EmailAction(logger, emailService, stringTemplateUtils, emailBuilder,
+ EmailAction emailAction = new EmailAction(logger, emailService, emailBuilder,
new Authentication("testname", "testpassword"), Profile.STANDARD, "testaccount", template, template, null, true);
//This is ok since the execution of the action only relies on the alert name
diff --git a/src/test/java/org/elasticsearch/alerts/actions/webhook/WebhookTest.java b/src/test/java/org/elasticsearch/alerts/actions/webhook/WebhookTest.java
deleted file mode 100644
index 82c50763f8e..00000000000
--- a/src/test/java/org/elasticsearch/alerts/actions/webhook/WebhookTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.actions.webhook;
-
-import org.elasticsearch.alerts.support.StringTemplateUtils;
-import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
-import org.elasticsearch.common.settings.ImmutableSettings;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.env.Environment;
-import org.elasticsearch.script.ScriptEngineService;
-import org.elasticsearch.script.ScriptService;
-import org.elasticsearch.script.mustache.MustacheScriptEngineService;
-import org.elasticsearch.test.ElasticsearchTestCase;
-import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.watcher.ResourceWatcherService;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- */
-public class WebhookTest extends ElasticsearchTestCase {
-
- public void testRequestParameterSerialization() throws Exception {
-
- Map responseMap = new HashMap<>();
- responseMap.put("hits",0);
-
- Settings settings = ImmutableSettings.settingsBuilder().build();
- MustacheScriptEngineService mustacheScriptEngineService = new MustacheScriptEngineService(settings);
- ThreadPool tp;
- tp = new ThreadPool(ThreadPool.Names.SAME);
- Set engineServiceSet = new HashSet<>();
- engineServiceSet.add(mustacheScriptEngineService);
- ScriptService scriptService = new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, tp));
-
- StringTemplateUtils.Template testTemplate = new StringTemplateUtils.Template("{ 'alertname' : '{{alert_name}}', 'response' : { 'hits' : {{response.hits}} } }");
- String testBody = WebhookAction.applyTemplate(new StringTemplateUtils(settings, ScriptServiceProxy.of(scriptService)), testTemplate, "foobar", responseMap);
-
- tp.shutdownNow();
- assertEquals("{ 'alertname' : 'foobar', 'response' : { 'hits' : 0 } }", testBody);
-
-
- }
-}
diff --git a/src/test/java/org/elasticsearch/alerts/support/template/ScriptTemplateTests.java b/src/test/java/org/elasticsearch/alerts/support/template/ScriptTemplateTests.java
new file mode 100644
index 00000000000..5880eee9546
--- /dev/null
+++ b/src/test/java/org/elasticsearch/alerts/support/template/ScriptTemplateTests.java
@@ -0,0 +1,171 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.alerts.support.init.proxy.ScriptServiceProxy;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.collect.ImmutableMap;
+import org.elasticsearch.common.settings.ImmutableSettings;
+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.Before;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ *
+ */
+public class ScriptTemplateTests extends ElasticsearchTestCase {
+
+ private ScriptServiceProxy proxy;
+ private ExecutableScript script;
+
+ @Before
+ public void init() throws Exception {
+ proxy = mock(ScriptServiceProxy.class);
+ script = mock(ExecutableScript.class);
+ }
+
+ @Test
+ public void testRender() throws Exception {
+ String lang = "_lang";
+ String templateText = "_template";
+ Map params = ImmutableMap.of("param_key", "param_val");
+ Map model = ImmutableMap.of("model_key", "model_val");
+ Map merged = ImmutableMap.builder().putAll(params).putAll(model).build();
+ ScriptService.ScriptType scriptType = ScriptService.ScriptType.values()[randomIntBetween(0, ScriptService.ScriptType.values().length - 1)];
+
+ when(script.run()).thenReturn("rendered_text");
+ when(proxy.executable(lang, templateText, scriptType, merged)).thenReturn(script);
+
+ ScriptTemplate template = new ScriptTemplate(proxy, templateText, lang, scriptType, params);
+ assertThat(template.render(model), is("rendered_text"));
+ }
+
+ @Test
+ public void testRender_OverridingModel() throws Exception {
+ String lang = "_lang";
+ String templateText = "_template";
+ Map params = ImmutableMap.of("key", "param_val");
+ Map model = ImmutableMap.of("key", "model_val");
+ ScriptService.ScriptType scriptType = randomScriptType();
+
+
+ when(script.run()).thenReturn("rendered_text");
+ when(proxy.executable(lang, templateText, scriptType, model)).thenReturn(script);
+
+ ScriptTemplate template = new ScriptTemplate(proxy, templateText, lang, scriptType, params);
+ assertThat(template.render(model), is("rendered_text"));
+ }
+
+ @Test
+ public void testRender_Defaults() throws Exception {
+ String templateText = "_template";
+ Map model = ImmutableMap.of("key", "model_val");
+
+ when(script.run()).thenReturn("rendered_text");
+ when(proxy.executable(ScriptTemplate.DEFAULT_LANG, templateText, ScriptService.ScriptType.INLINE, model)).thenReturn(script);
+
+ ScriptTemplate template = new ScriptTemplate(proxy, templateText);
+ assertThat(template.render(model), is("rendered_text"));
+ }
+
+ @Test
+ public void testParser() throws Exception {
+ ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
+
+ ScriptTemplate template = new ScriptTemplate(proxy, "_template", "_lang", randomScriptType(), ImmutableMap.of("param_key", "param_val"));
+
+ XContentBuilder builder = jsonBuilder().startObject()
+ .field(randomFrom("lang", "script_lang"), template.lang())
+ .field(randomFrom("script", "text"), template.text())
+ .field(randomFrom("type", "script_type"), template.type().name())
+ .field(randomFrom("params", "model"), template.params())
+ .endObject();
+ BytesReference bytes = builder.bytes();
+ XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
+ parser.nextToken();
+ ScriptTemplate parsed = templateParser.parse(parser);
+ assertThat(parsed, notNullValue());
+ assertThat(parsed, equalTo(template));
+ }
+
+ @Test
+ public void testParser_ParserSelfGenerated() throws Exception {
+ ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
+
+ ScriptTemplate template = new ScriptTemplate(proxy, "_template", "_lang", randomScriptType(), ImmutableMap.of("param_key", "param_val"));
+
+ XContentBuilder builder = jsonBuilder().value(template);
+ BytesReference bytes = builder.bytes();
+ XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
+ parser.nextToken();
+ ScriptTemplate parsed = templateParser.parse(parser);
+ assertThat(parsed, notNullValue());
+ assertThat(parsed, equalTo(template));
+ }
+
+ @Test(expected = Template.Parser.ParseException.class)
+ public void testParser_Invalid_UnexpectedField() throws Exception {
+ ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
+
+ XContentBuilder builder = jsonBuilder().startObject()
+ .field("unknown_field", "value")
+ .endObject();
+ BytesReference bytes = builder.bytes();
+ XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
+ parser.nextToken();
+ templateParser.parse(parser);
+ fail("expected parse exception when encountering an unknown field");
+ }
+
+ @Test(expected = Template.Parser.ParseException.class)
+ public void testParser_Invalid_UnknownScriptType() throws Exception {
+ ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
+
+ XContentBuilder builder = jsonBuilder().startObject()
+ .field("lang", ScriptTemplate.DEFAULT_LANG)
+ .field("script", "_template")
+ .field("type", "unknown_type")
+ .startObject("params").endObject()
+ .endObject();
+ BytesReference bytes = builder.bytes();
+ XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
+ parser.nextToken();
+ templateParser.parse(parser);
+ fail("expected parse exception when script type is unknown");
+ }
+
+ @Test(expected = Template.Parser.ParseException.class)
+ public void testParser_Invalid_MissingScript() throws Exception {
+ ScriptTemplate.Parser templateParser = new ScriptTemplate.Parser(ImmutableSettings.EMPTY, proxy);
+
+ XContentBuilder builder = jsonBuilder().startObject()
+ .field("lang", ScriptTemplate.DEFAULT_LANG)
+ .field("type", ScriptService.ScriptType.INDEXED)
+ .startObject("params").endObject()
+ .endObject();
+ BytesReference bytes = builder.bytes();
+ XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
+ parser.nextToken();
+ templateParser.parse(parser);
+ fail("expected parse exception when template text is missing");
+ }
+
+ private static ScriptService.ScriptType randomScriptType() {
+ return randomFrom(ScriptService.ScriptType.values());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/alerts/support/template/XContentTemplateTests.java b/src/test/java/org/elasticsearch/alerts/support/template/XContentTemplateTests.java
new file mode 100644
index 00000000000..5972197eda2
--- /dev/null
+++ b/src/test/java/org/elasticsearch/alerts/support/template/XContentTemplateTests.java
@@ -0,0 +1,82 @@
+/*
+ * 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.template;
+
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+/**
+ *
+ */
+public class XContentTemplateTests extends ElasticsearchTestCase {
+
+ @Test
+ public void testYaml() throws Exception {
+ Map model = new HashMap<>();
+ Map a = new HashMap<>();
+ a.put("aa", "aa");
+ a.put("ab", "ab");
+ model.put("a", a);
+ Map b = new HashMap<>();
+ b.put("ba", 21);
+ model.put("b", b);
+ model.put("c", "c");
+
+ String text = XContentTemplate.YAML.render(model);
+
+ // now lets read it as xcontent and make sure it has all the pieces
+ XContentParser parser = YamlXContent.yamlXContent.createParser(new BytesArray(text));
+ XContentParser.Token token = parser.nextToken();
+ assertThat(token, is(XContentParser.Token.START_OBJECT));
+ String currentFieldName = null;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if ("a".equals(currentFieldName)) {
+ assertThat(token, is(XContentParser.Token.START_OBJECT));
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else {
+ assertThat(token, is(XContentParser.Token.VALUE_STRING));
+ if ("aa".equals(currentFieldName)) {
+ assertThat(parser.text(), equalTo("aa"));
+ } else if ("ab".equals(currentFieldName)) {
+ assertThat(parser.text(), equalTo("ab"));
+ } else {
+ fail("unexpected field name [" + currentFieldName + "]");
+ }
+ }
+ }
+ } else if ("b".equals(currentFieldName)) {
+ assertThat(token, is(XContentParser.Token.START_OBJECT));
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else {
+ assertThat(token, is(XContentParser.Token.VALUE_NUMBER));
+ assertThat(currentFieldName, equalTo("ba"));
+ assertThat(parser.intValue(), is(21));
+ }
+ }
+ } else if ("c".equals(currentFieldName)) {
+ assertThat(token, is(XContentParser.Token.VALUE_STRING));
+ assertThat(parser.text(), equalTo("c"));
+ } else {
+ fail("unexpected field [" + currentFieldName + "]");
+ }
+ }
+ }
+}