diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Json.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Json.java new file mode 100644 index 00000000000..b8bdafe9fba --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Json.java @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.api; + +import org.elasticsearch.common.xcontent.DeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; + +import java.io.IOException; + +public class Json { + /** + * Load a string as the Java version of a JSON type, either List (JSON array), Map (JSON object), Number, Boolean or String + */ + public static Object load(String json) throws IOException{ + XContentParser parser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + json); + + switch (parser.nextToken()) { + case START_ARRAY: + return parser.list(); + case START_OBJECT: + return parser.map(); + case VALUE_NUMBER: + return parser.numberValue(); + case VALUE_BOOLEAN: + return parser.booleanValue(); + case VALUE_STRING: + return parser.text(); + default: + return null; + } + } + + /** + * Write a JSON representable type as a string + */ + public static String dump(Object data) throws IOException { + return dump(data, false); + } + + /** + * Write a JSON representable type as a string, optionally pretty print it by spanning multiple lines and indenting + */ + public static String dump(Object data, boolean pretty) throws IOException { + XContentBuilder builder = JsonXContent.contentBuilder(); + if (pretty) { + builder.prettyPrint(); + } + builder.value(data); + builder.flush(); + return builder.getOutputStream().toString(); + } +} diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/JsonTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/JsonTests.java new file mode 100644 index 00000000000..8136bbcce6b --- /dev/null +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/JsonTests.java @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +public class JsonTests extends ScriptTestCase { + + public void testDump() { + // simple object dump + Object output = exec("Json.dump(params.data)", singletonMap("data", singletonMap("hello", "world")), true); + assertEquals("{\"hello\":\"world\"}", output); + + output = exec("Json.dump(params.data)", singletonMap("data", singletonList(42)), true); + assertEquals("[42]", output); + + // pretty print + output = exec("Json.dump(params.data, true)", singletonMap("data", singletonMap("hello", "world")), true); + assertEquals("{\n \"hello\" : \"world\"\n}", output); + } + + public void testLoad() { + String json = "{\"hello\":\"world\"}"; + Object output = exec("Json.load(params.json)", singletonMap("json", json), true); + assertEquals(singletonMap("hello", "world"), output); + + json = "[42]"; + output = exec("Json.load(params.json)", singletonMap("json", json), true); + assertEquals(singletonList(42), output); + } + +} diff --git a/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.test b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.test index d4913ce5344..85493cad22d 100644 --- a/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.test +++ b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/spi/org.elasticsearch.painless.test @@ -5,6 +5,13 @@ class org.elasticsearch.script.JodaCompatibleZonedDateTime { (Instant, ZoneId) } +# for unit tests only +class org.elasticsearch.painless.api.Json { + def load(String) + String dump(def) + String dump(def,boolean) +} + class org.elasticsearch.painless.BindingsTests$BindingsTestScript { } diff --git a/x-pack/plugin/watcher/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/painless/70_json_in_watch.yml b/x-pack/plugin/watcher/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/painless/70_json_in_watch.yml new file mode 100644 index 00000000000..4edcdd9d41b --- /dev/null +++ b/x-pack/plugin/watcher/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/painless/70_json_in_watch.yml @@ -0,0 +1,58 @@ +--- +"Test json in watch": + + - do: + cluster.health: + wait_for_status: green + + - do: + watcher.put_watch: + id: "my_json_watch" + body: > + { + "trigger" : { + "schedule" : { "cron" : "0 0 0 1 * ? 2099" } + }, + "input" : { + "simple" : { + "count" : 1 + } + }, + "condition" : { + "script" : { + "source" : "String s = Json.dump(ctx.payload); Map m = Json.load(s); m.count == 1;", + "lang" : "painless" + } + }, + "transform" : { + "script": "String s = Json.dump(ctx.payload); Map m = Json.load(s); return ['test': m];" + }, + "actions" : { + "logging" : { + "logging" : { + "text" : "{{ctx.payload.test.count}}" + } + } + } + } + - match: { _id: "my_json_watch" } + + - do: + watcher.execute_watch: + id: "my_json_watch" + + - match: { "watch_record.watch_id": "my_json_watch" } + - match: { "watch_record.state": "executed" } + - match: { "watch_record.result.input.type": "simple" } + - match: { "watch_record.result.input.status": "success" } + - match: { "watch_record.result.input.payload.count": 1 } + - match: { "watch_record.result.condition.type": "script" } + - match: { "watch_record.result.condition.status": "success" } + - match: { "watch_record.result.condition.met": true } + - match: { "watch_record.result.transform.type": "script" } + - match: { "watch_record.result.transform.status": "success" } + - match: { "watch_record.result.transform.payload.test.count": 1 } + - match: { "watch_record.result.actions.0.id" : "logging" } + - match: { "watch_record.result.actions.0.type" : "logging" } + - match: { "watch_record.result.actions.0.status" : "success" } + - match: { "watch_record.result.actions.0.logging.logged_text" : "1" } diff --git a/x-pack/plugin/watcher/src/main/resources/org/elasticsearch/xpack/watcher/painless_whitelist.txt b/x-pack/plugin/watcher/src/main/resources/org/elasticsearch/xpack/watcher/painless_whitelist.txt index b1d9ab68a02..ebb03944563 100644 --- a/x-pack/plugin/watcher/src/main/resources/org/elasticsearch/xpack/watcher/painless_whitelist.txt +++ b/x-pack/plugin/watcher/src/main/resources/org/elasticsearch/xpack/watcher/painless_whitelist.txt @@ -8,3 +8,9 @@ class java.lang.String { String org.elasticsearch.painless.api.Augmentation sha1() String org.elasticsearch.painless.api.Augmentation sha256() } + +class org.elasticsearch.painless.api.Json { + def load(String) + String dump(def) + String dump(def, boolean) +}