This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch jq in repository https://gitbox.apache.org/repos/asf/camel.git
commit 399fdba662b5d26a0269f396ccb6761c1762edfa Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Feb 5 07:26:44 2024 +0100 CAMEL-20386: camel-jq - Add @Jq annotation for bean parameter binding as the other languages. Also fix so null result is returned as null, and not as 'null' string. --- .../main/java/org/apache/camel/language/jq/Jq.java | 45 +++++++++---- .../language/jq/JqAnnotationExpressionFactory.java | 73 ++++++++++++++++++++++ .../org/apache/camel/language/jq/JqExpression.java | 10 ++- .../org/apache/camel/language/jq/JqBeanTest.java | 73 ++++++++++++++++++++++ .../apache/camel/language/jq/JqExpressionTest.java | 14 +++-- 5 files changed, 197 insertions(+), 18 deletions(-) diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/Jq.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/Jq.java index 1eda7478817..307cbcdfccc 100644 --- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/Jq.java +++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/Jq.java @@ -16,21 +16,42 @@ */ package org.apache.camel.language.jq; -import org.apache.camel.CamelContext; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -public final class Jq { +import org.apache.camel.support.language.LanguageAnnotation; - private Jq() { - } +/** + * An annotation used to inject a JQ expression into a method parameter when using Bean Integration. + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@LanguageAnnotation(language = "jq", factory = JqAnnotationExpressionFactory.class) +public @interface Jq { + + String value(); + + /** + * The desired return type. + */ + Class<?> resultType() default Object.class; - public static JqExpression expression(String expression) { - return new JqExpression(expression); - } + /** + * The name of the variable we want to apply the expression to. + */ + String variableName() default ""; - public static JqExpression expression(CamelContext context, String expression) { - JqExpression answer = new JqExpression(expression); - answer.init(context); - return answer; - } + /** + * The name of the header we want to apply the expression to. + */ + String headerName() default ""; + /** + * The name of the exchange property we want to apply the expression to. + */ + String propertyName() default ""; } diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqAnnotationExpressionFactory.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqAnnotationExpressionFactory.java new file mode 100644 index 00000000000..20ecba2ddb3 --- /dev/null +++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqAnnotationExpressionFactory.java @@ -0,0 +1,73 @@ +/* + * 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.language.jq; + +import java.lang.annotation.Annotation; + +import org.apache.camel.CamelContext; +import org.apache.camel.Expression; +import org.apache.camel.support.builder.ExpressionBuilder; +import org.apache.camel.support.language.DefaultAnnotationExpressionFactory; +import org.apache.camel.support.language.LanguageAnnotation; +import org.apache.camel.util.ObjectHelper; + +public class JqAnnotationExpressionFactory extends DefaultAnnotationExpressionFactory { + + @Override + public Expression createExpression( + CamelContext camelContext, Annotation annotation, + LanguageAnnotation languageAnnotation, Class<?> expressionReturnType) { + + String expression = getExpressionFromAnnotation(annotation); + JqExpression answer = new JqExpression(expression); + + Class<?> resultType = getResultType(annotation); + if (resultType.equals(Object.class)) { + resultType = expressionReturnType; + } + if (resultType != null) { + answer.setResultType(resultType); + } + + if (annotation instanceof Jq) { + Jq jqAnnotation = (Jq) annotation; + + String variableName = null; + String headerName = null; + String propertyName = null; + if (ObjectHelper.isNotEmpty(jqAnnotation.variableName())) { + variableName = jqAnnotation.variableName(); + } + if (ObjectHelper.isNotEmpty(jqAnnotation.headerName())) { + headerName = jqAnnotation.headerName(); + } + if (ObjectHelper.isNotEmpty(jqAnnotation.propertyName())) { + propertyName = jqAnnotation.propertyName(); + } + if (variableName != null || headerName != null || propertyName != null) { + answer.setSource(ExpressionBuilder.singleInputExpression(variableName, headerName, propertyName)); + } + } + + answer.init(camelContext); + return answer; + } + + private Class<?> getResultType(Annotation annotation) { + return (Class<?>) getAnnotationObjectValue(annotation, "resultType"); + } +} diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java index 1c9e3f6b068..6f0029811d2 100644 --- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java +++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java @@ -156,11 +156,16 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT this.query.apply(scope, payload, outputs::add); if (outputs.size() == 1) { + JsonNode out = outputs.get(0); + // special if null + if (out.isNull()) { + return null; + } + // no need to convert output if (resultType == JsonNode.class) { - return outputs.get(0); + return out; } - return this.typeConverter.convertTo(resultType, exchange, outputs.get(0)); } else if (outputs.size() > 1) { // no need to convert outputs @@ -169,6 +174,7 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT } return outputs.stream() + .filter(o -> !o.isNull()) // skip null .map(item -> this.typeConverter.convertTo(resultType, exchange, item)) .collect(Collectors.toList()); } diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqBeanTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqBeanTest.java new file mode 100644 index 00000000000..8fadf0f8bdb --- /dev/null +++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqBeanTest.java @@ -0,0 +1,73 @@ +/* + * 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.language.jq; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +public class JqBeanTest extends CamelTestSupport { + + @Test + public void testFullName() throws Exception { + String json = "{\"person\" : {\"firstname\" : \"foo\", \"middlename\" : \"foo2\", \"lastname\" : \"bar\"}}"; + getMockEndpoint("mock:result").expectedBodiesReceived("foo foo2 bar"); + template.sendBody("direct:start", json); + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testFullNameTwo() throws Exception { + String json = "{\"person\" : {\"firstname\" : \"foo\", \"middlename\" : \"foo2\", \"lastname\" : \"bar\"}}"; + String json2 = "{\"person\" : {\"firstname\" : \"bar\", \"middlename\" : \"bar2\", \"lastname\" : \"foo\"}}"; + getMockEndpoint("mock:result").expectedBodiesReceived("foo foo2 bar", "bar bar2 foo"); + template.sendBody("direct:start", json); + template.sendBody("direct:start", json2); + MockEndpoint.assertIsSatisfied(context); + } + + @Test + public void testFirstAndLastName() throws Exception { + String json = "{\"person\" : {\"firstname\" : \"foo\", \"lastname\" : \"bar\"}}"; + getMockEndpoint("mock:result").expectedBodiesReceived("foo bar"); + template.sendBody("direct:start", json); + MockEndpoint.assertIsSatisfied(context); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").bean(FullNameBean.class).to("mock:result"); + } + }; + } + + protected static class FullNameBean { + public static String getName( + @Jq(".person.firstname") String first, + @Jq(value = ".person.middlename") String middle, + @Jq(".person.lastname") String last) { + if (middle != null) { + return first + " " + middle + " " + last; + } + return first + " " + last; + } + } +} diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionTest.java index 9e7fe8928cd..f35f446273f 100644 --- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionTest.java +++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionTest.java @@ -121,10 +121,10 @@ public class JqExpressionTest { exchange.getMessage().setBody(node); exchange.getMessage().setHeader("CommitterName", "Andrea"); - assertThat(Jq.expression(context, "has(\"baz\")").matches(exchange)).isTrue(); - assertThat(Jq.expression(context, "has(\"bar\")").matches(exchange)).isFalse(); - assertThat(Jq.expression(context, "header(\"CommitterName\") == \"Andrea\"").matches(exchange)).isTrue(); - assertThat(Jq.expression(context, "header(\"CommitterName\") != \"Andrea\"").matches(exchange)).isFalse(); + assertThat(jq(context, "has(\"baz\")").matches(exchange)).isTrue(); + assertThat(jq(context, "has(\"bar\")").matches(exchange)).isFalse(); + assertThat(jq(context, "header(\"CommitterName\") == \"Andrea\"").matches(exchange)).isTrue(); + assertThat(jq(context, "header(\"CommitterName\") != \"Andrea\"").matches(exchange)).isFalse(); } } @@ -262,4 +262,10 @@ public class JqExpressionTest { } } + private static JqExpression jq(CamelContext context, String expression) { + JqExpression answer = new JqExpression(expression); + answer.init(context); + return answer; + } + }