Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id AEB4C200D0C for ; Wed, 20 Sep 2017 21:05:06 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id AD0C71609D8; Wed, 20 Sep 2017 19:05:06 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id E69301609E2 for ; Wed, 20 Sep 2017 21:05:04 +0200 (CEST) Received: (qmail 1527 invoked by uid 500); 20 Sep 2017 19:05:04 -0000 Mailing-List: contact commits-help@camel.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@camel.apache.org Delivered-To: mailing list commits@camel.apache.org Received: (qmail 1518 invoked by uid 99); 20 Sep 2017 19:05:04 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 20 Sep 2017 19:05:04 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id ECCC2F325B; Wed, 20 Sep 2017 19:05:03 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: davsclaus@apache.org To: commits@camel.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: camel git commit: CAMEL-11796: Add headerName option to jsonpath expression. Also make jsonpath smarter to unwrap single element list result when converting to desired type. Date: Wed, 20 Sep 2017 19:05:03 +0000 (UTC) archived-at: Wed, 20 Sep 2017 19:05:06 -0000 Repository: camel Updated Branches: refs/heads/master 00d1d70ba -> af6889705 CAMEL-11796: Add headerName option to jsonpath expression. Also make jsonpath smarter to unwrap single element list result when converting to desired type. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/af688970 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/af688970 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/af688970 Branch: refs/heads/master Commit: af6889705e63d174e15a03f26fdc658c02871cb0 Parents: 00d1d70 Author: Claus Ibsen Authored: Wed Sep 20 21:04:45 2017 +0200 Committer: Claus Ibsen Committed: Wed Sep 20 21:04:54 2017 +0200 ---------------------------------------------------------------------- .../apache/camel/builder/ExpressionClause.java | 29 +++ .../camel/builder/ExpressionClauseSupport.java | 40 ++++ .../model/language/JsonPathExpression.java | 19 ++ .../src/main/docs/jsonpath-language.adoc | 204 +++++++++++-------- .../apache/camel/jsonpath/JsonPathEngine.java | 45 ++-- .../camel/jsonpath/JsonPathExpression.java | 25 ++- .../camel/jsonpath/JsonPathHeaderNameTest.java | 50 +++++ .../JsonPathTransformHeaderNameTest.java | 54 +++++ .../SpringJsonPathTransformHeaderNameTest.java | 46 +++++ .../SpringJsonPathTransformHeaderNameTest.xml | 36 ++++ .../JsonPathLanguageConfiguration.java | 12 ++ 11 files changed, 456 insertions(+), 104 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java index 9a95020..9e3106a 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java +++ b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java @@ -499,6 +499,21 @@ public class ExpressionClause extends ExpressionDefinition { /** * Evaluates a Json Path + * expression + * + * @param text the expression to be evaluated + * @param suppressExceptions whether to suppress exceptions such as PathNotFoundException + * @param resultType the return type expected by the expression + * @param headerName the name of the header to apply the expression to + * @return the builder to continue processing the DSL + */ + public T jsonpath(String text, boolean suppressExceptions, Class resultType, String headerName) { + return delegate.jsonpath(text, suppressExceptions, true, resultType, headerName); + } + + /** + * Evaluates a Json Path * expression with writeAsString enabled. * * @param text the expression to be evaluated @@ -522,6 +537,20 @@ public class ExpressionClause extends ExpressionDefinition { } /** + * Evaluates a Json Path + * expression with writeAsString enabled. + * + * @param text the expression to be evaluated + * @param suppressExceptions whether to suppress exceptions such as PathNotFoundException + * @param headerName the name of the header to apply the expression to + * @return the builder to continue processing the DSL + */ + public T jsonpathWriteAsString(String text, boolean suppressExceptions, String headerName) { + return delegate.jsonpathWriteAsString(text, suppressExceptions, true, headerName); + } + + /** * Evaluates a JXPath expression * * @param text the expression to be evaluated http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java index c1e487a..91b2d3f 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java +++ b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java @@ -429,6 +429,27 @@ public class ExpressionClauseSupport { /** * Evaluates a Json Path + * expression + * + * @param text the expression to be evaluated + * @param suppressExceptions whether to suppress exceptions such as PathNotFoundException + * @param allowSimple whether to allow in inlined simple exceptions in the json path expression + * @param resultType the return type expected by the expression + * @param headerName the name of the header to apply the expression to + * @return the builder to continue processing the DSL + */ + public T jsonpath(String text, boolean suppressExceptions, boolean allowSimple, Class resultType, String headerName) { + JsonPathExpression expression = new JsonPathExpression(text); + expression.setSuppressExceptions(suppressExceptions); + expression.setAllowSimple(allowSimple); + expression.setResultType(resultType); + expression.setHeaderName(headerName); + setExpressionType(expression); + return result; + } + + /** + * Evaluates a Json Path * expression with writeAsString enabled. * * @param text the expression to be evaluated @@ -471,6 +492,25 @@ public class ExpressionClauseSupport { } /** + * Evaluates a Json Path + * expression with writeAsString enabled. + * + * @param text the expression to be evaluated + * @param suppressExceptions whether to suppress exceptions such as PathNotFoundException + * @param allowSimple whether to allow in inlined simple exceptions in the json path expression + * @param headerName the name of the header to apply the expression to + * @return the builder to continue processing the DSL + */ + public T jsonpathWriteAsString(String text, boolean suppressExceptions, boolean allowSimple, String headerName) { + JsonPathExpression expression = new JsonPathExpression(text); + expression.setWriteAsString(true); + expression.setSuppressExceptions(suppressExceptions); + expression.setAllowSimple(allowSimple); + expression.setHeaderName(headerName); + return expression(expression); + } + + /** * Evaluates a JXPath expression * * @param text the expression to be evaluated http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/camel-core/src/main/java/org/apache/camel/model/language/JsonPathExpression.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/language/JsonPathExpression.java b/camel-core/src/main/java/org/apache/camel/model/language/JsonPathExpression.java index 574b752..0cf81d7 100644 --- a/camel-core/src/main/java/org/apache/camel/model/language/JsonPathExpression.java +++ b/camel-core/src/main/java/org/apache/camel/model/language/JsonPathExpression.java @@ -50,6 +50,8 @@ public class JsonPathExpression extends ExpressionDefinition { private Boolean allowEasyPredicate; @XmlAttribute @Metadata(defaultValue = "false") private Boolean writeAsString; + @XmlAttribute + private String headerName; public JsonPathExpression() { } @@ -124,6 +126,17 @@ public class JsonPathExpression extends ExpressionDefinition { this.writeAsString = writeAsString; } + public String getHeaderName() { + return headerName; + } + + /** + * Name of header to use as input, instead of the message body + */ + public void setHeaderName(String headerName) { + this.headerName = headerName; + } + public String getLanguage() { return "jsonpath"; } @@ -157,6 +170,9 @@ public class JsonPathExpression extends ExpressionDefinition { if (writeAsString != null) { setProperty(expression, "writeAsString", writeAsString); } + if (headerName != null) { + setProperty(expression, "headerName", headerName); + } super.configureExpression(camelContext, expression); } @@ -177,6 +193,9 @@ public class JsonPathExpression extends ExpressionDefinition { if (writeAsString != null) { setProperty(predicate, "writeAsString", writeAsString); } + if (headerName != null) { + setProperty(predicate, "headerName", headerName); + } super.configurePredicate(camelContext, predicate); } http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc b/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc index 3636a64..9d56b18 100644 --- a/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc +++ b/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc @@ -18,11 +18,11 @@ from("queue:books.new") .to("jms:queue:book.expensive") ----------------------------------------------------- -### JSonPath Options +=== JSonPath Options // language options: START -The JSonPath language supports 6 options which are listed below. +The JSonPath language supports 7 options which are listed below. @@ -34,82 +34,90 @@ The JSonPath language supports 6 options which are listed below. | allowSimple | true | Boolean | Whether to allow in inlined simple exceptions in the json path expression | allowEasyPredicate | true | Boolean | Whether to allow using the easy predicate parser to pre-parse predicates. | writeAsString | false | Boolean | Whether to write the output of each row/element as a JSon String value instead of a Map/POJO value. +| headerName | | String | Name of header to use as input instead of the message body | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks |=== // language options: END -### Using XML configuration +=== Using XML configuration If you prefer to configure your routes in your link:spring.html[Spring] XML file then you can use link:jsonpath.html[JSonPath] expressions as follows [source,xml] -------------------------------------------------------------------------- - - - - - - $.store.book[?(@.price < 10)] - - - - $.store.book[?(@.price < 30)] - - - - - - - - -------------------------------------------------------------------------- - -### Syntax +---- + + + + + + $.store.book[?(@.price < 10)] + + + + $.store.book[?(@.price < 30)] + + + + + + + + +---- + +=== Syntax See the https://code.google.com/p/json-path/[JSonPath] project page for further examples. -### Easy Syntax +=== Easy Syntax *Available as of Camel 2.19* When you just want to define a basic predicate using jsonpath syntax it can be a bit hard to remember the syntax. So for example to find out all the cheap books you have to do - $.store.book[?(@.price < 20)] +---- +$.store.book[?(@.price < 20)] +---- However what if you could just write it as - store.book.price < 20 +---- +store.book.price < 20 +---- And you can omit the path if you just want to look at nodes with a price key - price < 20 +---- +price < 20 +---- To support this there is a `EasyPredicateParser` which kicks-in if you have define the predicate using a basic style. That means the predicate must not start with the `$` sign, and only include one operator. The easy syntax is: - left OP right +---- +left OP right +---- You can use Camel simple language in the right operator, eg - store.book.price < ${header.limit} +---- +store.book.price < ${header.limit} +---- - - - -### Supported message body types +=== Supported message body types Camel JSonPath supports message body using the following types: [width="100%",cols="3m,7",options="header"] -|======================================================================= +|=== | Type | Comment | File | Reading from files | String | Plain strings @@ -119,13 +127,13 @@ Camel JSonPath supports message body using the following types: is able to use Jackson to read the message body as POJO and convert to `java.util.Map` which is supported by JSonPath. For example you can add `camel-jackson` as dependency to include Jackson. | InputStream | If none of the above types matches, then Camel will attempt to read the message body as an `java.io.InputStream`. -|======================================================================= +|=== If a message body is of unsupported type then an exception is thrown by default, however you can configure JSonPath to suppress exceptions (see below) -### Suppress exceptions +=== Suppress exceptions *Available as of Camel 2.16* @@ -136,7 +144,7 @@ contains optional data. Therefore you can set the option suppressExceptions to true to ignore this as shown: [source,java] ---------------------------------------------------- +---- from("direct:start") .choice() // use true to suppress exceptions @@ -144,31 +152,29 @@ from("direct:start") .to("mock:middle") .otherwise() .to("mock:other"); ---------------------------------------------------- +---- And in XML DSL: [source,xml] --------------------------------------------------------------------------- - - - - - person.middlename - - - - - - - --------------------------------------------------------------------------- - -  +---- + + + + + person.middlename + + + + + + + +---- This option is also available on the `@JsonPath` annotation. -### Inline Simple exceptions +=== Inline Simple exceptions *Available as of Camel 2.18* @@ -176,7 +182,7 @@ Its now possible to inlined Simple language expressions in the JSonPath expressi An example is shown below: [source,java] ---------------------------------------------------- +---- from("direct:start") .choice() .when().jsonpath("$.store.book[?(@.price < ${header.cheap})]") @@ -185,12 +191,12 @@ from("direct:start") .to("mock:average") .otherwise() .to("mock:expensive"); ---------------------------------------------------- +---- And in XML DSL: [source,xml] --------------------------------------------------------------------------- +---- @@ -207,24 +213,23 @@ And in XML DSL: --------------------------------------------------------------------------- +---- You can turn off support for inlined simple expression by setting the option allowSimple to false as shown: [source,java] ---------------------------------------------------- +---- .when().jsonpath("$.store.book[?(@.price < 10)]", false, false) ---------------------------------------------------- +---- And in XML DSL: [source,xml] --------------------------------------------------------------------------- +---- $.store.book[?(@.price < 10)] --------------------------------------------------------------------------- +---- - -### JSonPath injection +=== JSonPath injection You can use link:bean-integration.html[Bean Integration] to invoke a method on a bean and use various languages such as JSonPath to extract a @@ -233,7 +238,7 @@ value from the message and bind it to a method parameter. For example [source,java] ---------------------------------------------------------------------------------------------------- +---- public class Foo { @Consume(uri = "activemq:queue:books.new") @@ -241,9 +246,9 @@ public class Foo { // process the inbound message here } } ---------------------------------------------------------------------------------------------------- +---- -### Encoding Detection +=== Encoding Detection *Since Camel version 2.16*, the encoding of the JSON document is detected automatically, if the document is encoded in unicode  (UTF-8, @@ -253,35 +258,66 @@ you enter the document in String format to the JSONPath component or you can specify the encoding in the header "*CamelJsonPathJsonEncoding*" (JsonpathConstants.HEADER_JSON_ENCODING). -### Split JSon data into sub rows as JSon +=== Split JSon data into sub rows as JSon You can use jsonpath to split a JSon document, such as: [source,java] ---------------------------------------------------------------------------------------------------- - from("direct:start") - .split().jsonpath("$.store.book[*]") - .to("log:book"); ---------------------------------------------------------------------------------------------------- +---- +from("direct:start") + .split().jsonpath("$.store.book[*]") + .to("log:book"); +---- Then each book is logged, however the message body is a `Map` instance. Sometimes you may want to output this as plain String JSon value instead, which can be done from *Camel 2.20* onwards with the `writeAsString` option as shown: [source,java] ---------------------------------------------------------------------------------------------------- - from("direct:start") - .split().jsonpathWriteAsString("$.store.book[*]") - .to("log:book"); ---------------------------------------------------------------------------------------------------- +---- +from("direct:start") + .split().jsonpathWriteAsString("$.store.book[*]") + .to("log:book"); +---- Then each book is logged as a String JSon value. For earlier versions of Camel you would need to use camel-jackson dataformat and marshal the message body to make it convert the message body from `Map` to a `String` type. +=== Using header as input +*Available as of Camel 2.20* + +By default jsonpath uses the message body as the input source. However you can also use a header as input +by specifying the `headerName` option. + +For example to count the number of books from a json document that +was stored in a header named `books` you can do: + +[source,java] +---- +from("direct:start") + .setHeader("numberOfBooks") + .jsonpath("$..store.book.length()", false, int.class, "books") + .to("mock:result"); +---- + +In the `jsonpath` expression above we specify the name of the header as `books` +and we also told that we wanted the result to be converted as an integer by `int.class`. +The same example in XML DSL would be: + +[source,xml] +---- + + + + $..store.book.length() + + + +---- -### Dependencies +=== Dependencies To use JSonPath in your camel routes you need to add the a dependency on *camel-jsonpath* which implements the JSonPath language. @@ -291,10 +327,10 @@ substituting the version number for the latest & greatest release (see link:download.html[the download page for the latest versions]). [source,xml] ------------------------------------------ +---- org.apache.camel camel-jsonpath x.x.x ------------------------------------------ +---- http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java index d6d506c..d7be041 100644 --- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java +++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java @@ -52,18 +52,22 @@ public class JsonPathEngine { private static final Pattern SIMPLE_PATTERN = Pattern.compile("\\$\\{[^\\}]+\\}", Pattern.MULTILINE); private final String expression; private final boolean writeAsString; + private final String headerName; private final JsonPath path; private final Configuration configuration; private JsonPathAdapter adapter; private volatile boolean initJsonAdapter; + @Deprecated public JsonPathEngine(String expression) { - this(expression, false, false, true, null); + this(expression, false, false, true, null, null); } - public JsonPathEngine(String expression, boolean writeAsString, boolean suppressExceptions, boolean allowSimple, Option[] options) { + public JsonPathEngine(String expression, boolean writeAsString, boolean suppressExceptions, boolean allowSimple, + String headerName, Option[] options) { this.expression = expression; this.writeAsString = writeAsString; + this.headerName = headerName; Defaults defaults = DefaultsImpl.INSTANCE; if (options != null) { @@ -96,6 +100,7 @@ public class JsonPathEngine { } } + @SuppressWarnings("unchecked") public Object read(Exchange exchange) throws Exception { Object answer; if (path == null) { @@ -146,11 +151,9 @@ public class JsonPathEngine { } return map; } else { - if (adapter != null) { - String json = adapter.writeAsString(answer, exchange); - if (json != null) { - return json; - } + String json = adapter.writeAsString(answer, exchange); + if (json != null) { + return json; } } } @@ -159,12 +162,12 @@ public class JsonPathEngine { } private Object doRead(JsonPath path, Exchange exchange) throws IOException, CamelExchangeException { - Object json = exchange.getIn().getBody(); + Object json = headerName != null ? exchange.getIn().getHeader(headerName) : exchange.getIn().getBody(); if (json instanceof InputStream) { return readWithInputStream(path, exchange); } else if (json instanceof GenericFile) { - LOG.trace("JSonPath: {} is read as generic file from message body: {}", path, json); + LOG.trace("JSonPath: {} is read as generic file: {}", path, json); GenericFile genericFile = (GenericFile) json; if (genericFile.getCharset() != null) { // special treatment for generic file with charset @@ -174,19 +177,19 @@ public class JsonPathEngine { } if (json instanceof String) { - LOG.trace("JSonPath: {} is read as String from message body: {}", path, json); + LOG.trace("JSonPath: {} is read as String: {}", path, json); String str = (String) json; return path.read(str, configuration); } else if (json instanceof Map) { - LOG.trace("JSonPath: {} is read as Map from message body: {}", path, json); + LOG.trace("JSonPath: {} is read as Map: {}", path, json); Map map = (Map) json; return path.read(map, configuration); } else if (json instanceof List) { - LOG.trace("JSonPath: {} is read as List from message body: {}", path, json); + LOG.trace("JSonPath: {} is read as List: {}", path, json); List list = (List) json; return path.read(list, configuration); } else { - // can we find an adapter which can read the message body + // can we find an adapter which can read the message body/header Object answer = readWithAdapter(path, exchange); if (answer == null) { // fallback and attempt input stream for any other types @@ -207,12 +210,16 @@ public class JsonPathEngine { } // okay it was not then lets throw a failure - throw new CamelExchangeException("Cannot read message body as supported JSon value", exchange); + if (headerName != null) { + throw new CamelExchangeException("Cannot read message header " + headerName + " as supported JSon value", exchange); + } else { + throw new CamelExchangeException("Cannot read message body as supported JSon value", exchange); + } } private Object readWithInputStream(JsonPath path, Exchange exchange) throws IOException { - Object json = exchange.getIn().getBody(); - LOG.trace("JSonPath: {} is read as InputStream from message body: {}", path, json); + Object json = headerName != null ? exchange.getIn().getHeader(headerName) : exchange.getIn().getBody(); + LOG.trace("JSonPath: {} is read as InputStream: {}", path, json); InputStream is = exchange.getContext().getTypeConverter().tryConvertTo(InputStream.class, exchange, json); if (is != null) { @@ -232,8 +239,8 @@ public class JsonPathEngine { } private Object readWithAdapter(JsonPath path, Exchange exchange) { - Object json = exchange.getIn().getBody(); - LOG.trace("JSonPath: {} is read with adapter from message body: {}", path, json); + Object json = headerName != null ? exchange.getIn().getHeader(headerName) : exchange.getIn().getBody(); + LOG.trace("JSonPath: {} is read with adapter: {}", path, json); doInitAdapter(exchange); @@ -242,7 +249,7 @@ public class JsonPathEngine { Map map = adapter.readValue(json, exchange); if (map != null) { if (LOG.isDebugEnabled()) { - LOG.debug("JacksonJsonAdapter converted message body from: {} to: java.util.Map", ObjectHelper.classCanonicalName(json)); + LOG.debug("JacksonJsonAdapter converted object from: {} to: java.util.Map", ObjectHelper.classCanonicalName(json)); } return path.read(map, configuration); } http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java index c5bf74f..bbcd69b 100644 --- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java +++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java @@ -16,6 +16,9 @@ */ package org.apache.camel.jsonpath; +import java.util.Collection; +import java.util.List; + import com.jayway.jsonpath.Option; import org.apache.camel.AfterPropertiesConfigured; import org.apache.camel.CamelContext; @@ -40,6 +43,7 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper private boolean allowSimple = true; private boolean allowEasyPredicate = true; private boolean writeAsString; + private String headerName; private Option[] options; public JsonPathExpression(String expression) { @@ -113,6 +117,17 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper this.writeAsString = writeAsString; } + public String getHeaderName() { + return headerName; + } + + /** + * Name of header to use as input, instead of the message body + */ + public void setHeaderName(String headerName) { + this.headerName = headerName; + } + public Option[] getOptions() { return options; } @@ -129,6 +144,14 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper try { Object result = evaluateJsonPath(exchange, engine); if (resultType != null) { + // in some cases we get a single element that is wrapped in a List, so unwrap that + // if we for example want to grab the single entity and convert that to a int/boolean/String etc + boolean resultIsCollection = Collection.class.isAssignableFrom(resultType); + boolean singleElement = result instanceof List && ((List) result).size() == 1; + if (singleElement && !resultIsCollection) { + result = ((List) result).get(0); + LOG.trace("Unwrapping result: {} from single element List before converting to: {}", result, resultType); + } return exchange.getContext().getTypeConverter().convertTo(resultType, exchange, result); } else { return result; @@ -157,7 +180,7 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper LOG.debug("Initializing {} using: {}", predicate ? "predicate" : "expression", exp); try { - engine = new JsonPathEngine(exp, writeAsString, suppressExceptions, allowSimple, options); + engine = new JsonPathEngine(exp, writeAsString, suppressExceptions, allowSimple, headerName, options); } catch (Exception e) { throw new ExpressionIllegalSyntaxException(exp, e); } http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathHeaderNameTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathHeaderNameTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathHeaderNameTest.java new file mode 100644 index 0000000..5b1ea21 --- /dev/null +++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathHeaderNameTest.java @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.camel.jsonpath; + +import java.io.File; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class JsonPathHeaderNameTest extends CamelTestSupport { + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .setHeader("number").jsonpath("$..store.book.length()", false, int.class, "myHeader") + .to("mock:result"); + } + }; + } + + @Test + public void testAuthors() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("Hello World"); + getMockEndpoint("mock:result").expectedHeaderReceived("number", "2"); + + Object file = new File("src/test/resources/books.json"); + template.sendBodyAndHeader("direct:start", "Hello World", "myHeader", file); + + assertMockEndpointsSatisfied(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformHeaderNameTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformHeaderNameTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformHeaderNameTest.java new file mode 100644 index 0000000..1a12d1d --- /dev/null +++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathTransformHeaderNameTest.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.camel.jsonpath; + +import java.io.File; +import java.util.List; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class JsonPathTransformHeaderNameTest extends CamelTestSupport { + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .transform().jsonpath("$.store.book[*].author", false, null, "myHeader") + .to("mock:authors"); + } + }; + } + + @Test + public void testAuthors() throws Exception { + getMockEndpoint("mock:authors").expectedMessageCount(1); + + Object file = new File("src/test/resources/books.json"); + template.sendBodyAndHeader("direct:start", "Hello World", "myHeader", file); + + assertMockEndpointsSatisfied(); + + List authors = getMockEndpoint("mock:authors").getReceivedExchanges().get(0).getIn().getBody(List.class); + assertEquals("Nigel Rees", authors.get(0)); + assertEquals("Evelyn Waugh", authors.get(1)); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.java new file mode 100644 index 0000000..2699054 --- /dev/null +++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.camel.jsonpath; + +import java.io.File; +import java.util.List; + +import org.apache.camel.test.spring.CamelSpringTestSupport; +import org.junit.Test; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class SpringJsonPathTransformHeaderNameTest extends CamelSpringTestSupport { + + @Override + protected AbstractApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext("org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.xml"); + } + + @Test + public void testAuthors() throws Exception { + getMockEndpoint("mock:authors").expectedMessageCount(1); + + template.sendBodyAndHeader("direct:start", "Hello World", "myHeader", new File("src/test/resources/books.json")); + + assertMockEndpointsSatisfied(); + + List authors = getMockEndpoint("mock:authors").getReceivedExchanges().get(0).getIn().getBody(List.class); + assertEquals("Nigel Rees", authors.get(0)); + assertEquals("Evelyn Waugh", authors.get(1)); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/components/camel-jsonpath/src/test/resources/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.xml ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/test/resources/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.xml b/components/camel-jsonpath/src/test/resources/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.xml new file mode 100644 index 0000000..1bac174 --- /dev/null +++ b/components/camel-jsonpath/src/test/resources/org/apache/camel/jsonpath/SpringJsonPathTransformHeaderNameTest.xml @@ -0,0 +1,36 @@ + + + + + + + + + $.store.book[*].author + + + + + + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/af688970/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java ---------------------------------------------------------------------- diff --git a/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java b/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java index 54b098b..fda8cd6 100644 --- a/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java +++ b/platforms/spring-boot/components-starter/camel-jsonpath-starter/src/main/java/org/apache/camel/jsonpath/springboot/JsonPathLanguageConfiguration.java @@ -49,6 +49,10 @@ public class JsonPathLanguageConfiguration */ private Boolean writeAsString = false; /** + * Name of header to use as input instead of the message body + */ + private String headerName; + /** * Whether to trim the value to remove leading and trailing whitespaces and * line breaks */ @@ -86,6 +90,14 @@ public class JsonPathLanguageConfiguration this.writeAsString = writeAsString; } + public String getHeaderName() { + return headerName; + } + + public void setHeaderName(String headerName) { + this.headerName = headerName; + } + public Boolean getTrim() { return trim; }