CAMEL-10702: camel-jsonpath - Have an easy way of defining predicates with a single operator instead of having to grok the half complex style of jsonpath.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/cfbe791d Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/cfbe791d Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/cfbe791d Branch: refs/heads/master Commit: cfbe791d6f08f5a4063f7b93555563e041adf98c Parents: 69a19a3 Author: Claus Ibsen <davscl...@apache.org> Authored: Fri Jan 13 11:33:15 2017 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Fri Jan 13 12:30:42 2017 +0100 ---------------------------------------------------------------------- .../camel/jsonpath/JsonPathExpression.java | 22 ++++- .../apache/camel/jsonpath/JsonPathLanguage.java | 2 + .../easypredicate/EasyPredicateOperators.java | 96 ++++++++++++++++++++ .../easypredicate/EasyPredicateParser.java | 70 ++++++++++++++ .../jsonpath/JsonPathWithEvenSimpleCBRTest.java | 80 ++++++++++++++++ 5 files changed, 268 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/cfbe791d/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 37eeab1..3e79f9a 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 @@ -22,6 +22,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.ExpressionEvaluationException; import org.apache.camel.ExpressionIllegalSyntaxException; +import org.apache.camel.jsonpath.easypredicate.EasyPredicateParser; import org.apache.camel.support.ExpressionAdapter; public class JsonPathExpression extends ExpressionAdapter implements AfterPropertiesConfigured { @@ -29,6 +30,7 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper private final String expression; private JsonPathEngine engine; + private boolean predicate; private Class<?> resultType; private boolean suppressExceptions; private boolean allowSimple = true; @@ -38,6 +40,17 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper this.expression = expression; } + public boolean isPredicate() { + return predicate; + } + + /** + * Whether to be evaluated as a predicate + */ + public void setPredicate(boolean predicate) { + this.predicate = predicate; + } + public Class<?> getResultType() { return resultType; } @@ -102,10 +115,15 @@ public class JsonPathExpression extends ExpressionAdapter implements AfterProper } public void init() { + String exp = expression; + if (predicate) { + EasyPredicateParser parser = new EasyPredicateParser(); + exp = parser.parse(expression); + } try { - engine = new JsonPathEngine(expression, suppressExceptions, allowSimple, options); + engine = new JsonPathEngine(exp, suppressExceptions, allowSimple, options); } catch (Exception e) { - throw new ExpressionIllegalSyntaxException(expression, e); + throw new ExpressionIllegalSyntaxException(exp, e); } } http://git-wip-us.apache.org/repos/asf/camel/blob/cfbe791d/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java index 297823b..d2e4d0b 100644 --- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java +++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java @@ -58,6 +58,7 @@ public class JsonPathLanguage extends LanguageSupport { @Override public Predicate createPredicate(final String predicate) { JsonPathExpression answer = new JsonPathExpression(predicate); + answer.setPredicate(true); answer.setResultType(resultType); answer.setSuppressExceptions(suppressExceptions); answer.setOptions(options); @@ -68,6 +69,7 @@ public class JsonPathLanguage extends LanguageSupport { @Override public Expression createExpression(final String expression) { JsonPathExpression answer = new JsonPathExpression(expression); + answer.setPredicate(false); answer.setResultType(resultType); answer.setSuppressExceptions(suppressExceptions); answer.setOptions(options); http://git-wip-us.apache.org/repos/asf/camel/blob/cfbe791d/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateOperators.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateOperators.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateOperators.java new file mode 100644 index 0000000..4f49504 --- /dev/null +++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateOperators.java @@ -0,0 +1,96 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.easypredicate; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class EasyPredicateOperators { + + private static final String EQ = "=="; + private static final String NE = "!="; + private static final String LT = "<"; + private static final String LE = "<="; + private static final String GT = ">"; + private static final String GE = ">="; + private static final String REG = "=~"; + private static final String IN = "in"; + private static final String NIN = "nin"; + private static final String SIZE = "size"; + private static final String EMPTY = "empty"; + + private static final String[] OPS = new String[]{EQ, NE, LT, LE, GT, GE, REG, IN, NIN, SIZE, EMPTY}; + + private static final Pattern PATTERN = Pattern.compile("\\s(" + Arrays.stream(OPS).collect(Collectors.joining("|")) + ")\\s"); + + /** + * Does the expression have any operator? + */ + public static boolean hasOperator(String exp) { + return Arrays.stream(OPS).anyMatch(o -> exp.contains(" " + o + "")); + } + + /** + * Is this an operator + */ + static boolean isOperator(String exp) { + return Arrays.stream(OPS).anyMatch(s -> Objects.equals(s, exp)); + } + + /** + * Gets the operator + */ + static String getOperatorAtStart(String exp) { + return Arrays.stream(OPS).filter(o -> exp.startsWith(" " + o + "")).findFirst().orElse(null); + } + + public static String[] tokens(String exp) { + List<String> list = new ArrayList<>(); + + StringBuilder part = new StringBuilder(); + for (int i = 0; i < exp.length(); i++) { + + // is there a new operator + String s = exp.substring(i); + String op = getOperatorAtStart(s); + if (op != null) { + if (part.length() > 0) { + list.add(part.toString()); + part.setLength(0); + } + list.add(op.trim()); + // move i ahead + i = i + op.length() + 1; + } else { + char ch = exp.charAt(i); + part.append(ch); + } + } + + // ant leftovers + if (part.length() > 0) { + list.add(part.toString()); + } + + return list.toArray(new String[list.size()]); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/cfbe791d/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateParser.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateParser.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateParser.java new file mode 100644 index 0000000..f523f46 --- /dev/null +++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/easypredicate/EasyPredicateParser.java @@ -0,0 +1,70 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.easypredicate; + +import static org.apache.camel.jsonpath.easypredicate.EasyPredicateOperators.hasOperator; +import static org.apache.camel.jsonpath.easypredicate.EasyPredicateOperators.isOperator; +import static org.apache.camel.jsonpath.easypredicate.EasyPredicateOperators.tokens; + +public class EasyPredicateParser { + + public String parse(String exp) { + if (exp.startsWith("$")) { + // regular json path so skip + return exp; + } + + // must have an operator + if (!hasOperator(exp)) { + return exp; + } + + StringBuilder sb = new StringBuilder(); + + // grab before operator + String[] parts = tokens(exp); + + // only support one operator currently + if (parts.length == 3) { + String prev = parts[0]; + String op = parts[1]; + String next = parts[2]; + if (isOperator(op)) { + int pos = prev.lastIndexOf("."); + String before = prev.substring(0, pos); + String after = prev.substring(pos + 1); + sb.append("$"); + if (!before.startsWith(".")) { + sb.append("."); + } + sb.append(before); + sb.append("[?(@."); + sb.append(after); + sb.append(" "); + sb.append(op); + sb.append(" "); + sb.append(next); + sb.append(")]"); + } + return sb.toString(); + } + + // not able to parse so return as-is + return exp; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/cfbe791d/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWithEvenSimpleCBRTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWithEvenSimpleCBRTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWithEvenSimpleCBRTest.java new file mode 100644 index 0000000..8db54d5 --- /dev/null +++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathWithEvenSimpleCBRTest.java @@ -0,0 +1,80 @@ +/** + * 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 JsonPathWithEvenSimpleCBRTest extends CamelTestSupport { + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .choice() + .when().jsonpath("store.book.price < ${header.cheap}") + .to("mock:cheap") + .when().jsonpath("store.book.price < ${header.average}") + .to("mock:average") + .otherwise() + .to("mock:expensive"); + } + }; + } + + @Test + public void testCheap() throws Exception { + getMockEndpoint("mock:cheap").expectedMessageCount(1); + getMockEndpoint("mock:average").expectedMessageCount(0); + getMockEndpoint("mock:expensive").expectedMessageCount(0); + + fluentTemplate.withHeader("cheap", 10).withHeader("average", 30).withBody(new File("src/test/resources/cheap.json")) + .to("direct:start").send(); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testAverage() throws Exception { + getMockEndpoint("mock:cheap").expectedMessageCount(0); + getMockEndpoint("mock:average").expectedMessageCount(1); + getMockEndpoint("mock:expensive").expectedMessageCount(0); + + fluentTemplate.withHeader("cheap", 10).withHeader("average", 30).withBody(new File("src/test/resources/average.json")) + .to("direct:start").send(); + + assertMockEndpointsSatisfied(); + } + + @Test + public void testExpensive() throws Exception { + getMockEndpoint("mock:cheap").expectedMessageCount(0); + getMockEndpoint("mock:average").expectedMessageCount(0); + getMockEndpoint("mock:expensive").expectedMessageCount(1); + + fluentTemplate.withHeader("cheap", 10).withHeader("average", 30).withBody(new File("src/test/resources/expensive.json")) + .to("direct:start").send(); + + assertMockEndpointsSatisfied(); + } + +}