This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 892e225 [CAMEL-15804] - DataSonnet Expression Language Support (#4561) 892e225 is described below commit 892e2259cc90ae8ffded74a7b7bc02b34f2ba9dc Author: Jose Montoya <ja...@users.noreply.github.com> AuthorDate: Sat Dec 5 04:04:49 2020 -0600 [CAMEL-15804] - DataSonnet Expression Language Support (#4561) * datasonnet: adds language * sets up cml library * upgrade datasonnet mapper * formatting * refactors valuebuilder optional params * renames mimetype to mediatype also moves datasonnet builder to camel-datasonnet * Revert delegating ValueBuilder to camel-datasonnet This partially reverts commit d2202f7ee78daccacec2cec3f85925f4e8b8411e. * refactor headers and exchange properties to functions * defaults to output Document unless result specified * bump datasonnet-mapper ver * bump ds mapper version * add dep * fix scala directory * rebase 3.7.0-SNAPSHOT * fix cml library * adds docs * bump last ms3 ds version * Refactor reifier and language * adds ASF license where possible * uses parent/pom for dep versions * adds licenses in core-model * addresses checkstyle * align scala deps * uses parent/pom for dep versions * maven central mapper version * supportlevel preview * remvoe todos * Removed System print line in test * Replaced Spring DSL with XML DSL * Removed ms3inc repository from camel-datasonnet pom * clarify use of cml.properties in docs * fixes issues with mapper version * remove maven compiler properties * fix language test * moves classpath scanning to language * converts CML.scala to java * reverts scala deps changes * address checkstyle errors * fixed scala dep issue * move datasonnet expression builder * remove comment * bump ds mapper version Co-authored-by: Jake <jhug...@ms3-inc.com> --- bom/camel-bom/pom.xml | 5 + camel-dependencies/pom.xml | 2 + components/camel-datasonnet/pom.xml | 98 ++++++++ .../src/main/docs/datasonnet-language.adoc | 193 +++++++++++++++ .../org/apache/camel/language/datasonnet/CML.java | 115 +++++++++ .../camel/language/datasonnet/Datasonnet.java | 37 +++ .../language/datasonnet/DatasonnetConstants.java | 25 ++ .../language/datasonnet/DatasonnetExpression.java | 274 +++++++++++++++++++++ .../language/datasonnet/DatasonnetLanguage.java | 171 +++++++++++++ .../language/datasonnet/CamelDatasonnetTest.java | 164 ++++++++++++ .../language/datasonnet/ExpressionsInJavaTest.java | 118 +++++++++ .../apache/camel/language/datasonnet/Gizmo.java | 114 +++++++++ .../camel/language/datasonnet/Manufacturer.java | 68 +++++ .../camel/language/datasonnet/PropertiesTest.java | 52 ++++ .../camel-datasonnet/src/test/resources/dslibs.jar | Bin 0 -> 786 bytes .../src/test/resources/javaTest.json | 15 ++ .../test/resources/libraries/testlib4.libsonnet | 21 ++ .../src/test/resources/log4j2.properties | 32 +++ .../src/test/resources/namedImports.ds | 28 +++ .../src/test/resources/namedImportsFS.ds | 24 ++ .../src/test/resources/namedImports_result.json | 1 + .../camel/language.datasonnet/camel-context.xml | 172 +++++++++++++ .../src/test/resources/payload.csv | 2 + .../src/test/resources/payload.xml | 22 ++ .../src/test/resources/readCSVTest.ds | 20 ++ .../src/test/resources/readJavaTest.ds | 30 +++ .../src/test/resources/readXMLExtTest.ds | 23 ++ .../src/test/resources/readXMLExtTest.json | 13 + .../src/test/resources/simpleMapping.ds | 25 ++ .../src/test/resources/simpleMapping_payload.json | 4 + .../src/test/resources/simpleMapping_result.json | 1 + .../src/test/resources/testlib3.libsonnet | 21 ++ .../src/test/resources/writeJavaTest.ds | 30 +++ components/pom.xml | 1 + core/camel-allcomponents/pom.xml | 4 + .../org/apache/camel/builder/BuilderSupport.java | 35 +++ .../org/apache/camel/builder/ExpressionClause.java | 10 + .../camel/builder/ExpressionClauseSupport.java | 17 +- .../camel/model/language/DatasonnetExpression.java | 111 +++++++++ .../language/DatasonnetExpressionReifier.java | 62 +++++ .../camel/reifier/language/ExpressionReifier.java | 3 + parent/pom.xml | 8 + 42 files changed, 2168 insertions(+), 3 deletions(-) diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index b330711b..253aa81 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -624,6 +624,11 @@ </dependency> <dependency> <groupId>org.apache.camel</groupId> + <artifactId>camel-datasonnet</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> <artifactId>camel-debezium-common</artifactId> <version>${project.version}</version> </dependency> diff --git a/camel-dependencies/pom.xml b/camel-dependencies/pom.xml index 8483389..b10538b 100644 --- a/camel-dependencies/pom.xml +++ b/camel-dependencies/pom.xml @@ -161,6 +161,7 @@ <cxf.codegen.jvmArgs></cxf.codegen.jvmArgs> <cxf.codegenplugin.forkmode>once</cxf.codegenplugin.forkmode> <cxf.xjc.jvmArgs></cxf.xjc.jvmArgs> + <datasonnet-mapper-version>2.1.1</datasonnet-mapper-version> <debezium-mysql-connector-version>8.0.22</debezium-mysql-connector-version> <debezium-version>1.3.1.Final</debezium-version> <deltaspike-version>1.9.4</deltaspike-version> @@ -492,6 +493,7 @@ <rxjava2-version>2.2.20</rxjava2-version> <saxon-version>9.9.1-7</saxon-version> <scala-version>2.11.7</scala-version> + <scala-datasonnet-version>2.13.3</scala-datasonnet-version> <scribe-version>1.3.7</scribe-version> <servicemix-specs-version>2.9.0</servicemix-specs-version> <servlet-version-range>[3,4)</servlet-version-range> diff --git a/components/camel-datasonnet/pom.xml b/components/camel-datasonnet/pom.xml new file mode 100644 index 0000000..9187570 --- /dev/null +++ b/components/camel-datasonnet/pom.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>3.7.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-datasonnet</artifactId> + <packaging>jar</packaging> + + <name>Camel :: DataSonnet</name> + <description>Camel DataSonnet support</description> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <additionalClasspathElements> + <additionalClasspathElement>${project.basedir}/src/test/resources/dslibs.jar</additionalClasspathElement> + </additionalClasspathElements> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.scala-lang</groupId> + <artifactId>scala-library</artifactId> + <version>${scala-datasonnet-version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + <dependency> + <groupId>com.datasonnet</groupId> + <artifactId>datasonnet-mapper</artifactId> + <version>${datasonnet-mapper-version}</version> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>io.github.classgraph</groupId> + <artifactId>classgraph</artifactId> + <version>${classgraph-version}</version> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.skyscreamer</groupId> + <artifactId>jsonassert</artifactId> + <version>${jsonassert-version}</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc b/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc new file mode 100644 index 0000000..5f2ea2d --- /dev/null +++ b/components/camel-datasonnet/src/main/docs/datasonnet-language.adoc @@ -0,0 +1,193 @@ +[[datasonnet-language]] += DataSonnet Language +:docTitle: DataSonnet +:artifactId: camel-datasonnet +:description: To use DataSonnet scripts in Camel expressions or predicates. +:since: 3.7 +:supportLevel: Preview +include::{cq-version}@camel-quarkus:ROOT:partial$reference/languages/datasonnet.adoc[opts=optional] + +*Since Camel {since}* + +Camel supports https://datasonnet.com/[DataSonnet] transformations to allow an Expression or Predicate to be used in the Java DSL or xref:manual::xml-configuration.adoc[XML +Configuration]. + +To use a DataSonnet expression use the following Java code: +[source,java] +--------------------------------------- +... datasonnet("someDSExpression") ... +--------------------------------------- + +== Example + +Here is a simple example using a DataSonnet expression as a predicate in a Message Filter: + +[source,java] +------------------------------------------------------------------------------------------------ +// lets route if a line item is over $100 +from("queue:foo") + .filter(datasonnet("ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null")) + .to("queue:bar") +------------------------------------------------------------------------------------------------ + +And the XML DSL: + +[source,xml] +----------------------------------------------------------------------------- +<route> + <from uri="queue:foo"/> + <filter> + <datasonnet>ds.arrays.firstWith(body.lineItems, function(item) item > 100) != null</datasonnet> + <to uri="queue:bar"/> + </filter> +</route> +----------------------------------------------------------------------------- + +Here is an example of a simple DataSonnet expression as a transformation EIP. This example will transform an XML body with +`lineItems` into JSON while filtering out lines that are under 100. + +[source,java] +------------------------------------------------------------------------------------------------ +from("queue:foo") + .transform(datasonnet("ds.filter(body.lineItems, function(item) item > 100)", String.class) + .bodyMediaType("application/xml").outputMediaType("application/json") + ) + .to("queue:bar") +------------------------------------------------------------------------------------------------ + +And the XML DSL: + +[source,xml] +----------------------------------------------------------------------------- +<route> + <from uri="queue:foo"/> + <filter> + <datasonnet bodyMediaType="application/xml" outputMediaType="application/json" resultTypeName="java.lang.String" > + ds.filter(body.lineItems, function(item) item > 100) + </datasonnet> + <to uri="queue:bar"/> + </filter> +</route> +----------------------------------------------------------------------------- + +== Setting result type + +The xref:datasonnet-language.adoc[DataSonnet] expression will return a `com.datasonnet.document.Document` by default. The +document preserves the content type metadata along with the contents of the result of the transformation. In predicates, +however, the Document will be automatically unwrapped and the boolean content will be returned. Similarly any times you +want the content in a specific result type like a String. To do this you have to instruct the +xref:datasonnet-language.adoc[DataSonnet] which result type to return. + +In Java DSL: + +[source,java] +---- +datasonnet("body.foo", String.class) +---- + +In XML DSL you use the *resultType* attribute to provide a fully +qualified classname: + +[source,xml] +---- +<datasonnet resultType="java.lang.String">body.foo</datasonnet> +---- + +If the expression results in an array, or an object, you can instruct the expression to return you `List.class` +or `Map.class`, respectively. However, you must also set the output media type to `application/x-java-object`. + +NOTE: The default `Document` object is useful in situations where there are intermediate transformation steps, and so +retaining the content metadata through a route execution is valuable. + +== Specifying Media Types + +Traditionally the input and output media types are specified through the +https://datasonnet.s3-us-west-2.amazonaws.com/docs-ci/primary/master/datasonnet/1.0-SNAPSHOT/headers.html[DataSonnet Header] +The xref:datasonnet-language.adoc[DataSonnet] expression provides convenience options for specifying the body and output +media types without the need for a Header, this is useful if the transformation is a one-liner, for example. + +The DataSonnet expression will look for a body media type in the following order: + +1. If the body is a `Document` it will use the metadata in the object +2. If the convenience bodyMediaType method was used, it will use its value +3. A "CamelDatasonnetBodyMediaType" exchange property +4. A "Content-Type" message header +5. The DataSonnet Header payload media type directive +6. `application/x-java-object` + +And for output media type: + +1. If the convenience outputMediaType method was used, it will use its value +2. A "CamelDatasonnetOutputMediaType" exchange property +3. A "CamelDatasonnetOutputMediaType" message header +4. The DataSonnet Header output media type directive +5. `application/x-java-object` + +== Functions + +Camel adds the following DataSonnet functions that can be used to access the +exchange: + +[width="100%",cols="10%,10%,10%,70%",options="header",] +|=== +|Function |Argument |Type |Description + +|cml.properties |key for property |String |To lookup a property using the +xref:ROOT:properties-component.adoc[Properties] component (property placeholders). + +|cml.header |the header name |String |Will return the message header. + +|cml.exchangeProperty |key for property |String |Will return the exchange property. +|=== + +Here's an example showing some of these functions in use: + +[source,java] +------------------------------------------------------------------------------------------------ +from("direct:in") + .setBody(datasonnet("'hello, ' + cml.properties('toGreet')", String.class)) + .to("mock:camel"); +------------------------------------------------------------------------------------------------ + +And the XML DSL: + +[source,xml] +----------------------------------------------------------------------------- +<route> + <from uri="direct:in"/> + <setBody> + <datasonnet resultTypeName="java.lang.String">'hello, ' + cml.properties('toGreet')</datasonnet> + </setBody> + <to uri="mock:camel"/> +</route> +----------------------------------------------------------------------------- + +== Loading script from external resource + +You can externalize the script and have Camel load it from a resource +such as `"classpath:"`, `"file:"`, or `"http:"`. + +This is done using the following syntax: `"resource:scheme:location"`, +eg to refer to a file on the classpath you can do: + +[source,java] +------------------------------------------------------------------- +.setHeader("myHeader").datasonnet("resource:classpath:mydatasonnet.ds") +------------------------------------------------------------------- + +== Dependencies + +To use scripting languages in your camel routes you need to add a +dependency on *camel-datasonnet*. + +If you use Maven you could just add the following to your `pom.xml`, +substituting the version number for the latest and greatest release (see +the download page for the latest versions). + +[source,xml] +--------------------------------------- +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-datasonnet</artifactId> + <version>x.x.x</version> +</dependency> +--------------------------------------- diff --git a/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/CML.java b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/CML.java new file mode 100644 index 0000000..0716ff4 --- /dev/null +++ b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/CML.java @@ -0,0 +1,115 @@ +/* + * 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.datasonnet; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.datasonnet.document.DefaultDocument; +import com.datasonnet.document.Document; +import com.datasonnet.document.MediaTypes; +import com.datasonnet.header.Header; +import com.datasonnet.spi.DataFormatService; +import com.datasonnet.spi.Library; +import com.datasonnet.spi.PluginException; +import org.apache.camel.Exchange; +import sjsonnet.Materializer; +import sjsonnet.Val; + +public final class CML extends Library { + private static final CML INSTANCE = new CML(); + private final ThreadLocal<Exchange> exchange = new ThreadLocal<>(); + + private CML() { + } + + public static CML getInstance() { + return INSTANCE; + } + + public ThreadLocal<Exchange> getExchange() { + return exchange; + } + + @Override + public String namespace() { + return "cml"; + } + + @Override + public Set<String> libsonnets() { + return Collections.emptySet(); + } + + @Override + public Map<String, Val.Func> functions(DataFormatService dataFormats, Header header) { + Map<String, Val.Func> answer = new HashMap<>(); + answer.put("properties", makeSimpleFunc( + Collections.singletonList("key"), //parameters list + params -> properties(params.get(0)))); + answer.put("header", makeSimpleFunc( + Collections.singletonList("key"), //parameters list + params -> header(params.get(0), dataFormats))); + answer.put("exchangeProperty", makeSimpleFunc( + Collections.singletonList("key"), //parameters list + params -> exchangeProperty(params.get(0), dataFormats))); + + return answer; + } + + public Map<String, Val.Obj> modules(DataFormatService dataFormats, Header header) { + return Collections.emptyMap(); + } + + private Val properties(Val key) { + if (key instanceof Val.Str) { + return new Val.Str(exchange.get().getContext().resolvePropertyPlaceholders("{{" + ((Val.Str) key).value() + "}}")); + } + throw new IllegalArgumentException("Expected String got: " + key.prettyName()); + } + + private Val header(Val key, DataFormatService dataformats) { + if (key instanceof Val.Str) { + return valFrom(exchange.get().getMessage().getHeader(((Val.Str) key).value()), dataformats); + } + throw new IllegalArgumentException("Expected String got: " + key.prettyName()); + } + + private Val exchangeProperty(Val key, DataFormatService dataformats) { + if (key instanceof Val.Str) { + return valFrom(exchange.get().getProperty(((Val.Str) key).value()), dataformats); + } + throw new IllegalArgumentException("Expected String got: " + key.prettyName()); + } + + private Val valFrom(Object obj, DataFormatService dataformats) { + Document doc; + if (obj instanceof Document) { + doc = (Document) obj; + } else { + doc = new DefaultDocument(obj, MediaTypes.APPLICATION_JAVA); + } + + try { + return Materializer.reverse(dataformats.mandatoryRead(doc)); + } catch (PluginException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/Datasonnet.java b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/Datasonnet.java new file mode 100644 index 0000000..6e5de5f --- /dev/null +++ b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/Datasonnet.java @@ -0,0 +1,37 @@ +/* + * 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.datasonnet; + +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; + +import org.apache.camel.support.language.LanguageAnnotation; + +/** + * Used to inject a DataSonnet expression into a field, property, method or parameter when using + * <a href="http://camel.apache.org/bean-integration.html">Bean Integration</a>. + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +@LanguageAnnotation(language = "datasonnet") +public @interface Datasonnet { + String value(); +} diff --git a/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetConstants.java b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetConstants.java new file mode 100644 index 0000000..5ee7a7c --- /dev/null +++ b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetConstants.java @@ -0,0 +1,25 @@ +/* + * 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.datasonnet; + +public final class DatasonnetConstants { + public static final String BODY_MEDIATYPE = "CamelDatasonnetBodyMediaType"; + public static final String OUTPUT_MEDIATYPE = "CamelDatasonnetOutputMediaType"; + + private DatasonnetConstants() { + } +} diff --git a/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetExpression.java b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetExpression.java new file mode 100644 index 0000000..1c1db0b --- /dev/null +++ b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetExpression.java @@ -0,0 +1,274 @@ +/* + * 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.datasonnet; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import com.datasonnet.Mapper; +import com.datasonnet.MapperBuilder; +import com.datasonnet.document.DefaultDocument; +import com.datasonnet.document.Document; +import com.datasonnet.document.MediaType; +import com.datasonnet.document.MediaTypes; +import org.apache.camel.Exchange; +import org.apache.camel.Expression; +import org.apache.camel.RuntimeExpressionException; +import org.apache.camel.spi.ExpressionResultTypeAware; +import org.apache.camel.support.ExchangeHelper; +import org.apache.camel.support.ExpressionAdapter; +import org.apache.camel.support.MessageHelper; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DatasonnetExpression extends ExpressionAdapter implements ExpressionResultTypeAware { + private static final Logger LOGGER = LoggerFactory.getLogger(DatasonnetExpression.class); + + private String expression; + private Expression metaExpression; + private MediaType bodyMediaType; + private MediaType outputMediaType; + private Class<?> resultType; + private Collection<String> libraryPaths; + + public DatasonnetExpression(String expression) { + this.expression = expression; + } + + public DatasonnetExpression(Expression expression) { + this.metaExpression = expression; + } + + public static DatasonnetExpression builder(String expression) { + DatasonnetExpression answer = new DatasonnetExpression(expression); + return answer; + } + + public static DatasonnetExpression builder(Expression expression) { + DatasonnetExpression answer = new DatasonnetExpression(expression); + return answer; + } + + public static DatasonnetExpression builder(String expression, Class<?> resultType) { + DatasonnetExpression answer = new DatasonnetExpression(expression); + answer.setResultType(resultType); + return answer; + } + + public static DatasonnetExpression builder(Expression expression, Class<?> resultType) { + DatasonnetExpression answer = new DatasonnetExpression(expression); + answer.setResultType(resultType); + return answer; + } + + @Override + public boolean matches(Exchange exchange) { + this.outputMediaType = MediaTypes.APPLICATION_JAVA; + return evaluate(exchange, Boolean.class); + } + + @SuppressWarnings("unchecked") + @Override + public <T> T evaluate(Exchange exchange, Class<T> type) { + try { + if (metaExpression != null) { + expression = metaExpression.evaluate(exchange, String.class); + } + + Objects.requireNonNull(expression, "String expression property must be set!"); + + Document<?> result = doEvaluate(exchange); + if (!type.equals(Object.class)) { + return ExchangeHelper.convertToType(exchange, type, result.getContent()); + } else if (resultType == null || resultType.equals(Document.class)) { + return (T) result; + } else { + return (T) result.getContent(); + } + } catch (Exception e) { + throw new RuntimeExpressionException("Unable to evaluate DataSonnet expression : " + expression, e); + } finally { + CML.getInstance().getExchange().remove(); + } + } + + private Document<?> doEvaluate(Exchange exchange) { + if (bodyMediaType == null) { + //Try to auto-detect input mime type if it was not explicitly set + String typeHeader = exchange.getProperty(DatasonnetConstants.BODY_MEDIATYPE, + exchange.getIn().getHeader(Exchange.CONTENT_TYPE), String.class); + if (typeHeader != null) { + bodyMediaType = MediaType.valueOf(typeHeader); + } + } + + Document<?> body; + if (exchange.getMessage().getBody() instanceof Document) { + body = (Document<?>) exchange.getMessage().getBody(); + } else if (MediaTypes.APPLICATION_JAVA.equalsTypeAndSubtype(bodyMediaType)) { + body = new DefaultDocument<>(exchange.getMessage().getBody()); + } else { + body = new DefaultDocument<>(MessageHelper.extractBodyAsString(exchange.getMessage()), bodyMediaType); + } + + Map<String, Document<?>> inputs = Collections.singletonMap("body", body); + + DatasonnetLanguage language = (DatasonnetLanguage) exchange.getContext().resolveLanguage("datasonnet"); + Mapper mapper = language.computeIfMiss(expression, () -> new MapperBuilder(expression) + .withInputNames(inputs.keySet()) + .withImports(resolveImports(language)) + .withLibrary(CML.getInstance()) + .withDefaultOutput(MediaTypes.APPLICATION_JAVA) + .build()); + + // pass exchange to CML lib using thread as context + CML.getInstance().getExchange().set(exchange); + + if (outputMediaType == null) { + //Try to auto-detect output mime type if it was not explicitly set + String typeHeader = exchange.getProperty(DatasonnetConstants.OUTPUT_MEDIATYPE, + exchange.getIn().getHeader(DatasonnetConstants.OUTPUT_MEDIATYPE), String.class); + if (typeHeader != null) { + outputMediaType = MediaType.valueOf(typeHeader); + } else { + outputMediaType = MediaTypes.ANY; + } + } + + if (resultType == null || resultType.equals(Document.class)) { + return mapper.transform(body, inputs, outputMediaType, Object.class); + } else { + return mapper.transform(body, inputs, outputMediaType, resultType); + } + } + + private Map<String, String> resolveImports(DatasonnetLanguage language) { + if (libraryPaths == null) { + return language.getClasspathImports(); + } + + Map<String, String> answer = new HashMap<>(); + LOGGER.debug("Explicit library path is " + libraryPaths); + for (String nextPath : libraryPaths) { + final File nextLibDir = new File(nextPath); + if (nextLibDir.isDirectory()) { + try { + Files.walkFileTree(nextLibDir.toPath(), new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + File f = file.toFile(); + if (!f.isDirectory() && f.getName().toLowerCase().endsWith(".libsonnet")) { + String content = IOUtils.toString(file.toUri()); + Path relative = nextLibDir.toPath().relativize(file); + LOGGER.debug("Loading DataSonnet library: " + relative); + answer.put(relative.toString(), content); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.error("Unable to load libraries from " + nextPath, e); + } + } + } + + return answer; + } + + // Getter/Setter methods + // ------------------------------------------------------------------------- + + public MediaType getBodyMediaType() { + return bodyMediaType; + } + + /** + * The message's body MediaType + */ + public void setBodyMediaType(MediaType inputMimeType) { + this.bodyMediaType = inputMimeType; + } + + public MediaType getOutputMediaType() { + return outputMediaType; + } + + /** + * The MediaType to output + */ + public void setOutputMediaType(MediaType outputMimeType) { + this.outputMediaType = outputMimeType; + } + + public Collection<String> getLibraryPaths() { + return libraryPaths; + } + + /** + * The paths to search for .libsonnet files + */ + public void setLibraryPaths(Collection<String> libraryPaths) { + this.libraryPaths = libraryPaths; + } + + @Override + public String getExpressionText() { + return this.expression; + } + + @Override + public Class<?> getResultType() { + return this.resultType; + } + + /** + * Sets the class of the result type (type from output). + * <p/> + * The default result type is com.datasonnet.document.Document + */ + public void setResultType(Class<?> targetType) { + this.resultType = targetType; + } + + // Fluent builder methods + // ------------------------------------------------------------------------- + public DatasonnetExpression bodyMediaType(MediaType bodyMediaType) { + setBodyMediaType(bodyMediaType); + return this; + } + + public DatasonnetExpression outputMediaType(MediaType outputMediaType) { + setOutputMediaType(outputMediaType); + return this; + } + + @Override + public String toString() { + return "datasonnet: " + expression; + } +} diff --git a/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetLanguage.java b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetLanguage.java new file mode 100644 index 0000000..c7053f3 --- /dev/null +++ b/components/camel-datasonnet/src/main/java/org/apache/camel/language/datasonnet/DatasonnetLanguage.java @@ -0,0 +1,171 @@ +/* + * 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.datasonnet; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import com.datasonnet.Mapper; +import com.datasonnet.document.MediaType; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ScanResult; +import org.apache.camel.CamelContext; +import org.apache.camel.Expression; +import org.apache.camel.Predicate; +import org.apache.camel.spi.PropertyConfigurer; +import org.apache.camel.spi.annotations.Language; +import org.apache.camel.support.LRUCacheFactory; +import org.apache.camel.support.LanguageSupport; +import org.apache.camel.support.component.PropertyConfigurerSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Language("datasonnet") +public class DatasonnetLanguage extends LanguageSupport implements PropertyConfigurer { + private static final Logger LOGGER = LoggerFactory.getLogger(DatasonnetLanguage.class); + + private static final Map<String, String> CLASSPATH_IMPORTS = new HashMap<>(); + + static { + LOGGER.debug("One time classpath search..."); + try (ScanResult scanResult = new ClassGraph().whitelistPaths("/").scan()) { + scanResult.getResourcesWithExtension("libsonnet") + .forEachByteArray((resource, bytes) -> { + LOGGER.debug("Loading DataSonnet library: " + resource.getPath()); + CLASSPATH_IMPORTS.put(resource.getPath(), new String(bytes, StandardCharsets.UTF_8)); + }); + } + } + + // Cache used to stores the Mappers + // See: {@link GroovyLanguage} + private final Map<String, Mapper> mapperCache = LRUCacheFactory.newLRUSoftCache(16, 1000, true); + + private MediaType bodyMediaType; + private MediaType outputMediaType; + private Class<?> resultType; + private Collection<String> libraryPaths; + + @Override + public Predicate createPredicate(String expression) { + return createPredicate(expression, null); + } + + @Override + public Expression createExpression(String expression) { + return createExpression(expression, null); + } + + @Override + public Predicate createPredicate(String expression, Object[] properties) { + return (Predicate) createExpression(expression, properties); + } + + @Override + public Expression createExpression(String expression, Object[] properties) { + expression = loadResource(expression); + + DatasonnetExpression answer = new DatasonnetExpression(expression); + answer.setResultType(property(Class.class, properties, 0, resultType)); + + String stringBodyMediaType = property(String.class, properties, 1, null); + answer.setBodyMediaType(stringBodyMediaType != null ? MediaType.valueOf(stringBodyMediaType) : bodyMediaType); + String stringOutputMediaType = property(String.class, properties, 2, null); + answer.setOutputMediaType(stringOutputMediaType != null ? MediaType.valueOf(stringOutputMediaType) : outputMediaType); + + return answer; + } + + Optional<Mapper> lookup(String script) { + return Optional.ofNullable(mapperCache.get(script)); + } + + Mapper computeIfMiss(String script, Supplier<Mapper> mapperSupplier) { + return mapperCache.computeIfAbsent(script, k -> mapperSupplier.get()); + } + + public Map<String, String> getClasspathImports() { + return CLASSPATH_IMPORTS; + } + + @Override + public boolean configure(CamelContext camelContext, Object target, String name, Object value, boolean ignoreCase) { + if (target != this) { + throw new IllegalStateException("Can only configure our own instance !"); + } + + switch (ignoreCase ? name.toLowerCase() : name) { + case "bodyMediaType": + case "bodymediatype": + setBodyMediaType(PropertyConfigurerSupport.property(camelContext, String.class, value)); + return true; + case "outputMediaType": + case "outputmediatype": + setOutputMediaType(PropertyConfigurerSupport.property(camelContext, String.class, value)); + return true; + case "resultType": + case "resulttype": + setResultType(PropertyConfigurerSupport.property(camelContext, Class.class, value)); + return true; + default: + return false; + } + } + + // Getter/Setter methods + // ------------------------------------------------------------------------- + + public MediaType getBodyMediaType() { + return bodyMediaType; + } + + public void setBodyMediaType(MediaType bodyMediaType) { + this.bodyMediaType = bodyMediaType; + } + + public void setBodyMediaType(String bodyMediaType) { + this.bodyMediaType = MediaType.valueOf(bodyMediaType); + } + + public MediaType getOutputMediaType() { + return outputMediaType; + } + + public void setOutputMediaType(MediaType outputMediaType) { + this.outputMediaType = outputMediaType; + } + + public void setOutputMediaType(String outputMediaType) { + this.outputMediaType = MediaType.valueOf(outputMediaType); + } + + public Collection<String> getLibraryPaths() { + return libraryPaths; + } + + public void setLibraryPaths(Collection<String> libraryPaths) { + this.libraryPaths = libraryPaths; + } + + public void setResultType(Class<?> targetType) { + this.resultType = targetType; + } +} diff --git a/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/CamelDatasonnetTest.java b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/CamelDatasonnetTest.java new file mode 100644 index 0000000..a4f2938 --- /dev/null +++ b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/CamelDatasonnetTest.java @@ -0,0 +1,164 @@ +/* + * 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.datasonnet; + +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.TimeZone; + +import com.datasonnet.document.Document; +import org.apache.camel.Exchange; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.support.ExchangeHelper; +import org.apache.camel.test.spring.junit5.CamelSpringTestSupport; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CamelDatasonnetTest extends CamelSpringTestSupport { + private MockEndpoint mock; + + @Override + protected AbstractApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext("org/apache/camel/language.datasonnet/camel-context.xml"); + } + + @Test + public void testTransform() throws Exception { + runCamelTest(loadResourceAsString("simpleMapping_payload.json"), + loadResourceAsString("simpleMapping_result.json"), + "direct:basicTransform"); + } + + @Test + public void testTransformXML() throws Exception { + runCamelTest(loadResourceAsString("payload.xml"), + loadResourceAsString("readXMLExtTest.json"), + "direct:transformXML"); + } + + @Test + public void testTransformCSV() throws Exception { + runCamelTest(loadResourceAsString("payload.csv"), + "{\"account\":\"123\"}", + "direct:transformCSV"); + } + + @Test + public void testDatasonnetScript() throws Exception { + runCamelTest(loadResourceAsString("simpleMapping_payload.json"), + loadResourceAsString("simpleMapping_result.json"), + "direct:datasonnetScript"); + } + + @Test + public void testNamedImports() throws Exception { + runCamelTest("{}", + loadResourceAsString("namedImports_result.json"), + "direct:namedImports"); + } + + @Test + public void testExpressionLanguage() throws Exception { + runCamelTest("World", + "{ \"test\":\"Hello, World\"}", + "direct:expressionLanguage"); + } + + @Test + public void testNullInput() throws Exception { + runCamelTest("", + "{ \"test\":\"Hello, World\"}", + "direct:nullInput"); + runCamelTest(null, + "{ \"test\":\"Hello, World\"}", + "direct:nullInput"); + } + + @Test + public void testReadJava() throws Exception { + Gizmo theGizmo = new Gizmo(); + theGizmo.setName("gizmo"); + theGizmo.setQuantity(123); + theGizmo.setInStock(true); + theGizmo.setColors(Arrays.asList("red", "white", "blue")); + + Manufacturer manufacturer = new Manufacturer(); + manufacturer.setManufacturerName("ACME Corp."); + manufacturer.setManufacturerCode("ACME123"); + theGizmo.setManufacturer(manufacturer); + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + theGizmo.setDate(df.parse("2020-01-06")); + + runCamelTest(theGizmo, + loadResourceAsString("javaTest.json"), + "direct:readJava"); + } + + @Test + public void testWriteJava() throws Exception { + Gizmo theGizmo = new Gizmo(); + theGizmo.setName("gizmo"); + theGizmo.setQuantity(123); + theGizmo.setInStock(true); + theGizmo.setColors(Arrays.asList("red", "white", "blue")); + + Manufacturer manufacturer = new Manufacturer(); + manufacturer.setManufacturerName("ACME Corp."); + manufacturer.setManufacturerCode("ACME123"); + theGizmo.setManufacturer(manufacturer); + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + theGizmo.setDate(df.parse("2020-01-06")); + + String payload = loadResourceAsString("javaTest.json"); + + template.sendBody("direct:writeJava", payload); + mock = getMockEndpoint("mock:direct:end"); + Exchange exchange = mock.assertExchangeReceived(mock.getReceivedCounter() - 1); + Object response = exchange.getIn().getBody(); + + assertEquals(theGizmo, response); + } + + private void runCamelTest(Object payload, String expectedJson, String uri) throws Exception { + template.sendBody(uri, payload); + mock = getMockEndpoint("mock:direct:end"); + Exchange exchange = mock.assertExchangeReceived(mock.getReceivedCounter() - 1); + Object body = exchange.getMessage().getBody(); + String response; + if (body instanceof Document) { + response = ExchangeHelper.convertToMandatoryType(exchange, String.class, ((Document<?>) body).getContent()); + } else { + response = exchange.getMessage().getBody(String.class); + + } + JSONAssert.assertEquals(expectedJson, response, true); + } + + private String loadResourceAsString(String name) throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream(name); + return IOUtils.toString(is); + } +} diff --git a/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/ExpressionsInJavaTest.java b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/ExpressionsInJavaTest.java new file mode 100644 index 0000000..6d843a3 --- /dev/null +++ b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/ExpressionsInJavaTest.java @@ -0,0 +1,118 @@ +/* + * 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.datasonnet; + +import java.util.Collections; +import java.util.List; + +import com.datasonnet.document.MediaTypes; +import org.apache.camel.EndpointInject; +import org.apache.camel.Exchange; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +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; +import org.skyscreamer.jsonassert.JSONAssert; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ExpressionsInJavaTest extends CamelTestSupport { + @EndpointInject("mock:direct:response") + protected MockEndpoint endEndpoint; + + @Produce("direct:expressionsInJava") + protected ProducerTemplate expressionsInJavaProducer; + + @Produce("direct:chainExpressions") + protected ProducerTemplate chainExpressionsProducer; + + @Produce("direct:fluentBuilder") + protected ProducerTemplate fluentBuilderProducer; + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:chainExpressions") + .setHeader("ScriptHeader", constant("{ hello: \"World\"}")) + .setBody(datasonnet(simple("${header.ScriptHeader}", String.class))) + .to("mock:direct:response"); + + from("direct:expressionsInJava") + .choice() + .when(datasonnet("payload == 'World'")) + .setBody(datasonnet("'Hello, ' + payload", String.class)) + .otherwise() + .setBody(datasonnet("'Good bye, ' + payload", String.class)) + .end() + .to("mock:direct:response"); + + from("direct:fluentBuilder") + // no optional params, look in header + .setHeader(Exchange.CONTENT_TYPE, constant(MediaTypes.APPLICATION_JAVA_VALUE)) + .setBody(DatasonnetExpression.builder("payload")) + .removeHeader(Exchange.CONTENT_TYPE) + + // override output + .transform(DatasonnetExpression.builder("payload", String.class) + .outputMediaType(MediaTypes.APPLICATION_JSON)) + + // override input + .transform( + DatasonnetExpression.builder("payload", List.class).bodyMediaType(MediaTypes.APPLICATION_JSON)) + + // override both + .setHeader(Exchange.CONTENT_TYPE, constant(MediaTypes.APPLICATION_JSON_VALUE)) + .setBody(constant("<root>some-value</root>")) + .transform(DatasonnetExpression.builder("payload.root['$']", String.class) + .bodyMediaType(MediaTypes.APPLICATION_XML) + .outputMediaType(MediaTypes.APPLICATION_JSON)) + .to("mock:direct:response"); + } + }; + } + + @Test + public void testExpressionLanguageInJava() throws Exception { + endEndpoint.expectedMessageCount(1); + expressionsInJavaProducer.sendBody("World"); + Exchange exchange = endEndpoint.assertExchangeReceived(endEndpoint.getReceivedCounter() - 1); + String response = exchange.getIn().getBody().toString(); + assertEquals("Hello, World", response); + } + + @Test + public void testChainExpressions() throws Exception { + endEndpoint.expectedMessageCount(1); + chainExpressionsProducer.sendBody("{}"); + Exchange exchange = endEndpoint.assertExchangeReceived(endEndpoint.getReceivedCounter() - 1); + String response = exchange.getIn().getBody().toString(); + JSONAssert.assertEquals("{\"hello\":\"World\"}", response, true); + } + + @Test + public void testFluentBuilder() throws Exception { + endEndpoint.expectedMessageCount(1); + fluentBuilderProducer.sendBody(Collections.singletonList("datasonnet")); + Exchange exchange = endEndpoint.assertExchangeReceived(endEndpoint.getReceivedCounter() - 1); + String response = exchange.getMessage().getBody(String.class); + assertEquals("\"some-value\"", response); + } +} diff --git a/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/Gizmo.java b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/Gizmo.java new file mode 100644 index 0000000..743320e --- /dev/null +++ b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/Gizmo.java @@ -0,0 +1,114 @@ +/* + * 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.datasonnet; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +public class Gizmo { + private String name; + private int quantity; + private List<String> colors; + private boolean inStock; + private Manufacturer manufacturer; + private Date date; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public List<String> getColors() { + return colors; + } + + public void setColors(List<String> colors) { + this.colors = colors; + } + + public boolean isInStock() { + return inStock; + } + + public void setInStock(boolean inStock) { + this.inStock = inStock; + } + + public Manufacturer getManufacturer() { + return manufacturer; + } + + public void setManufacturer(Manufacturer manufacturer) { + this.manufacturer = manufacturer; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + @Override + public String toString() { + return "Gizmo{" + + "name='" + name + '\'' + + ", quantity=" + quantity + + ", colors=" + colors + + ", inStock=" + inStock + + ", manufacturer=" + manufacturer + + ", date=" + date + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Gizmo gizmo = (Gizmo) o; + return getQuantity() == gizmo.getQuantity() && + isInStock() == gizmo.isInStock() && + Objects.equals(getName(), gizmo.getName()) && + Objects.equals(getColors(), gizmo.getColors()) && + Objects.equals(date, gizmo.getDate()) && + Objects.equals(getManufacturer(), gizmo.getManufacturer()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getQuantity(), getColors(), isInStock(), getManufacturer(), getDate()); + } +} diff --git a/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/Manufacturer.java b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/Manufacturer.java new file mode 100644 index 0000000..c903041 --- /dev/null +++ b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/Manufacturer.java @@ -0,0 +1,68 @@ +/* + * 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.datasonnet; + +import java.util.Objects; + +public class Manufacturer { + private String manufacturerName; + private String manufacturerCode; + + public String getManufacturerName() { + return manufacturerName; + } + + public void setManufacturerName(String manufacturerName) { + this.manufacturerName = manufacturerName; + } + + public String getManufacturerCode() { + return manufacturerCode; + } + + public void setManufacturerCode(String manufacturerCode) { + this.manufacturerCode = manufacturerCode; + } + + @Override + public String toString() { + return "Manufacturer{" + + "manufacturerName='" + manufacturerName + '\'' + + ", manufacturerCode='" + manufacturerCode + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Manufacturer that = (Manufacturer) o; + return Objects.equals(getManufacturerName(), that.getManufacturerName()) && + Objects.equals(getManufacturerCode(), that.getManufacturerCode()); + } + + @Override + public int hashCode() { + return Objects.hash(getManufacturerName(), getManufacturerCode()); + } +} diff --git a/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/PropertiesTest.java b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/PropertiesTest.java new file mode 100644 index 0000000..be61462 --- /dev/null +++ b/components/camel-datasonnet/src/test/java/org/apache/camel/language/datasonnet/PropertiesTest.java @@ -0,0 +1,52 @@ +/* + * 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.datasonnet; + +import java.util.Properties; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.support.DefaultExchange; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +public class PropertiesTest extends CamelTestSupport { + @Test + public void testPropertiesBuiltin() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:camel"); + mock.expectedBodiesReceived("bar"); + + template.send("direct:in", new DefaultExchange(context)); + + mock.assertIsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + Properties prop = new Properties(); + prop.setProperty("foo", "bar"); + context.getPropertiesComponent().setInitialProperties(prop); + + from("direct:in") + .setBody(datasonnet("cml.properties('foo')", String.class)) + .to("mock:camel"); + } + }; + } +} diff --git a/components/camel-datasonnet/src/test/resources/dslibs.jar b/components/camel-datasonnet/src/test/resources/dslibs.jar new file mode 100644 index 0000000..64c90c8 Binary files /dev/null and b/components/camel-datasonnet/src/test/resources/dslibs.jar differ diff --git a/components/camel-datasonnet/src/test/resources/javaTest.json b/components/camel-datasonnet/src/test/resources/javaTest.json new file mode 100644 index 0000000..2dffc04 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/javaTest.json @@ -0,0 +1,15 @@ +{ + "pojoColors": [ + "red", + "white", + "blue" + ], + "pojoInStock": true, + "pojoManufacturer": { + "manufacturerCode": "ACME123", + "manufacturerName": "ACME Corp." + }, + "pojoName": "gizmo", + "pojoQuantity": 123, + "pojoDate": "2020-01-06" +} diff --git a/components/camel-datasonnet/src/test/resources/libraries/testlib4.libsonnet b/components/camel-datasonnet/src/test/resources/libraries/testlib4.libsonnet new file mode 100644 index 0000000..d6fdda1 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/libraries/testlib4.libsonnet @@ -0,0 +1,21 @@ +/* + * 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. + */ + +{ + sayBye(name):: + "Bye, " + name + " : TestLib4" +} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/log4j2.properties b/components/camel-datasonnet/src/test/resources/log4j2.properties new file mode 100644 index 0000000..8f2e7bb --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/log4j2.properties @@ -0,0 +1,32 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-datasonnet-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n +logger.springframework.name = org.springframework +logger.springframework.level = WARN +logger.datasonnet.name=org.apache.camel.language.datasonnet +logger.datasonnet.level=DEBUG +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file diff --git a/components/camel-datasonnet/src/test/resources/namedImports.ds b/components/camel-datasonnet/src/test/resources/namedImports.ds new file mode 100644 index 0000000..dbf0a4f --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/namedImports.ds @@ -0,0 +1,28 @@ +/* + * 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. + */ + +local testlib1 = import 'testlib.libsonnet'; +local testlib2 = import 'libraries/testlib2.libsonnet'; +local testlib3 = import 'testlib3.libsonnet'; +local testlib4 = import 'libraries/testlib4.libsonnet'; + +{ + "Lib1JAR": testlib1.sayHello("World"), + "Lib2JAR": testlib2.sayBye("World"), + "Lib3FS": testlib3.sayHello("World"), + "Lib4FS": testlib4.sayBye("World") +} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/namedImportsFS.ds b/components/camel-datasonnet/src/test/resources/namedImportsFS.ds new file mode 100644 index 0000000..be16794 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/namedImportsFS.ds @@ -0,0 +1,24 @@ +/* + * 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. + */ + +local testlib3 = import 'testlib3.libsonnet'; +local testlib4 = import 'libraries/testlib4.libsonnet'; + +{ + "Lib3FS": testlib3.sayHello("World"), + "Lib4FS": testlib4.sayBye("World") +} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/namedImports_result.json b/components/camel-datasonnet/src/test/resources/namedImports_result.json new file mode 100644 index 0000000..56fcae1 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/namedImports_result.json @@ -0,0 +1 @@ +{"Lib1JAR":"Hello, World","Lib2JAR":"Bye, World","Lib3FS":"Hello, World : TestLib3","Lib4FS":"Bye, World : TestLib4"} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/org/apache/camel/language.datasonnet/camel-context.xml b/components/camel-datasonnet/src/test/resources/org/apache/camel/language.datasonnet/camel-context.xml new file mode 100644 index 0000000..0a9cac1 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/org/apache/camel/language.datasonnet/camel-context.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<!-- Configures the Camel Context--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd +"> + + <camelContext id="main" xmlns="http://camel.apache.org/schema/spring"> + <route id="basicTransform"> + <from uri="direct:basicTransform"/> + + <setProperty name="test"> + <constant>HelloWorld</constant> + </setProperty> + <setProperty name="count"> + <simple resultType="java.lang.Integer">1</simple> + </setProperty> + <setProperty name="isActive"> + <simple resultType="java.lang.Boolean">true</simple> + </setProperty> + <setProperty name="1. Full Name"> + <constant>DataSonnet</constant> + </setProperty> + + <transform> + <datasonnet bodyMediaType="application/json" outputMediaType="application/json" + resultType="java.lang.String">resource:classpath:simpleMapping.ds</datasonnet> + </transform> + <to uri="mock:direct:end"/> + </route> + + <route id="transformXML"> + <from uri="direct:transformXML"/> + <transform> + <datasonnet bodyMediaType="application/xml" outputMediaType="application/json" + resultType="java.lang.String">resource:classpath:readXMLExtTest.ds</datasonnet> + </transform> + <to uri="mock:direct:end"/> + </route> + + <route id="transformCSV"> + <from uri="direct:transformCSV"/> + <transform> + <datasonnet bodyMediaType="application/csv" outputMediaType="application/json" + resultType="java.lang.String">resource:classpath:readCSVTest.ds</datasonnet> + </transform> + <to uri="mock:direct:end"/> + </route> + + <route id="datasonnetScript"> + <from uri="direct:datasonnetScript"/> + + <setProperty name="test"> + <simple>HelloWorld</simple> + </setProperty> + <setProperty name="count"> + <simple resultType="java.lang.Integer">1</simple> + </setProperty> + <setProperty name="isActive"> + <simple resultType="java.lang.Boolean">true</simple> + </setProperty> + <setProperty name="1. Full Name"> + <constant>DataSonnet</constant> + </setProperty> + + <transform> + <datasonnet bodyMediaType="application/json" outputMediaType="application/json" + resultType="java.lang.String"><![CDATA[ +{ + "uid": payload.userId, + "uname": payload.name, + "testVar": cml.exchangeProperty('test'), + "isActive": cml.exchangeProperty('isActive'), + "count": cml.exchangeProperty('count'), + "fullName": cml.exchangeProperty('1. Full Name') +} +]]> + </datasonnet> + </transform> + + <to uri="mock:direct:end"/> + </route> + + <route id="namedImports"> + <from uri="direct:namedImports"/> + <transform> + <datasonnet bodyMediaType="application/json" outputMediaType="application/json" + resultType="java.lang.String">resource:classpath:namedImports.ds</datasonnet> + </transform> + <to uri="mock:direct:end"/> + </route> + + <route id="readJava"> + <from uri="direct:readJava"/> + <transform> + <datasonnet bodyMediaType="application/x-java-object" outputMediaType="application/json" + resultType="java.lang.String">resource:classpath:readJavaTest.ds</datasonnet> + </transform> + <to uri="mock:direct:end"/> + </route> + + <route id="writeJava"> + <from uri="direct:writeJava"/> + <transform> + <datasonnet bodyMediaType="application/json" outputMediaType="application/x-java-object" + resultType="org.apache.camel.language.datasonnet.Gizmo">resource:classpath:writeJavaTest.ds</datasonnet> + </transform> + <to uri="mock:direct:end"/> + </route> + + <route id="expressionLanguage"> + <from uri="direct:expressionLanguage"/> + + <setHeader name="CamelDatasonnetOutputMediaType"> + <constant>text/plain</constant> + </setHeader> + <setHeader name="HelloHeader"> + <language language="datasonnet">"Hello, " + payload</language> + </setHeader> + + <setHeader name="CamelDatasonnetOutputMediaType"> + <constant>application/json</constant> + </setHeader> + <setBody> + <language language="datasonnet"> + <![CDATA[ + { + test: cml.header('HelloHeader') + } + ]]> + </language> + </setBody> + + <to uri="mock:direct:end"/> + </route> + + <route id="nullInput"> + <from uri="direct:nullInput"/> + <setBody> + <datasonnet outputMediaType="application/json" resultType="java.lang.String"> + { + test: "Hello, World" + } + </datasonnet> + </setBody> + + <to uri="mock:direct:end"/> + </route> + + </camelContext> + +</beans> diff --git a/components/camel-datasonnet/src/test/resources/payload.csv b/components/camel-datasonnet/src/test/resources/payload.csv new file mode 100644 index 0000000..75676b4 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/payload.csv @@ -0,0 +1,2 @@ +account,firstName,lastName +123,Joe,Doe diff --git a/components/camel-datasonnet/src/test/resources/payload.xml b/components/camel-datasonnet/src/test/resources/payload.xml new file mode 100644 index 0000000..f65ee14 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/payload.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<test:root xmlns:test="http://www.modusbox.com"> + <test:datasonnet version="1.0">Hello World</test:datasonnet> +</test:root> diff --git a/components/camel-datasonnet/src/test/resources/readCSVTest.ds b/components/camel-datasonnet/src/test/resources/readCSVTest.ds new file mode 100644 index 0000000..4d908e3 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/readCSVTest.ds @@ -0,0 +1,20 @@ +/* + * 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. + */ + +{ + account: payload[0]["account"] +} diff --git a/components/camel-datasonnet/src/test/resources/readJavaTest.ds b/components/camel-datasonnet/src/test/resources/readJavaTest.ds new file mode 100644 index 0000000..9150608 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/readJavaTest.ds @@ -0,0 +1,30 @@ +/** DataSonnet +version=2.0 +input payload application/x-java-object; DateFormat=yyyy-MM-dd +*/ + +/* + * 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. + */ + +{ + "pojoName": payload.name, + "pojoQuantity": payload.quantity, + "pojoInStock": payload.inStock, + "pojoColors": payload.colors, + "pojoManufacturer": payload.manufacturer, + "pojoDate": payload.date +} diff --git a/components/camel-datasonnet/src/test/resources/readXMLExtTest.ds b/components/camel-datasonnet/src/test/resources/readXMLExtTest.ds new file mode 100644 index 0000000..3b4a4de --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/readXMLExtTest.ds @@ -0,0 +1,23 @@ +/** DataSonnet +version=2.0 +input payload application/xml; NamespaceSeparator=%; TextValueKey=__text; AttributeCharacter=* +*/ + +/* + * 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. + */ + +payload diff --git a/components/camel-datasonnet/src/test/resources/readXMLExtTest.json b/components/camel-datasonnet/src/test/resources/readXMLExtTest.json new file mode 100644 index 0000000..0e4ca56 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/readXMLExtTest.json @@ -0,0 +1,13 @@ +{ + "test%root": { + "*xmlns": { + "test": "http://www.modusbox.com" + }, + "test%datasonnet": { + "*version": "1.0", + "__text": "Hello World", + "~": 1 + }, + "~": 1 + } +} diff --git a/components/camel-datasonnet/src/test/resources/simpleMapping.ds b/components/camel-datasonnet/src/test/resources/simpleMapping.ds new file mode 100644 index 0000000..a1faf25 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/simpleMapping.ds @@ -0,0 +1,25 @@ +/* + * 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. + */ + +{ + "uid": payload.userId, + "uname": payload.name, + "testVar": cml.exchangeProperty('test'), + "isActive": cml.exchangeProperty('isActive'), + "count": cml.exchangeProperty('count'), + "fullName": cml.exchangeProperty('1. Full Name') +} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/simpleMapping_payload.json b/components/camel-datasonnet/src/test/resources/simpleMapping_payload.json new file mode 100644 index 0000000..1ab6bf4 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/simpleMapping_payload.json @@ -0,0 +1,4 @@ +{ + "userId": 123, + "name": "JavaDuke" +} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/simpleMapping_result.json b/components/camel-datasonnet/src/test/resources/simpleMapping_result.json new file mode 100644 index 0000000..e9489bd --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/simpleMapping_result.json @@ -0,0 +1 @@ +{"count":1,"isActive":true,"testVar":"HelloWorld","uid":123,"uname":"JavaDuke","fullName": "DataSonnet"} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/testlib3.libsonnet b/components/camel-datasonnet/src/test/resources/testlib3.libsonnet new file mode 100644 index 0000000..38878b3 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/testlib3.libsonnet @@ -0,0 +1,21 @@ +/* + * 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. + */ + +{ + sayHello(name):: + "Hello, " + name + " : TestLib3" +} \ No newline at end of file diff --git a/components/camel-datasonnet/src/test/resources/writeJavaTest.ds b/components/camel-datasonnet/src/test/resources/writeJavaTest.ds new file mode 100644 index 0000000..d1e98b7 --- /dev/null +++ b/components/camel-datasonnet/src/test/resources/writeJavaTest.ds @@ -0,0 +1,30 @@ +/** DataSonnet +version=2.0 +output application/x-java-object; DateFormat=yyyy-MM-dd +*/ + +/* + * 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. + */ + +{ + "name": payload.pojoName, + "quantity": payload.pojoQuantity, + "inStock": payload.pojoInStock, + "colors": payload.pojoColors, + "manufacturer": payload.pojoManufacturer, + "date": payload.pojoDate +} diff --git a/components/pom.xml b/components/pom.xml index 29770f7..b77caf8 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -175,6 +175,7 @@ <module>camel-crypto</module> <module>camel-csimple-joor</module> <module>camel-csv</module> + <module>camel-datasonnet</module> <module>camel-debezium-common</module> <module>camel-debezium-mongodb</module> <module>camel-debezium-mysql</module> diff --git a/core/camel-allcomponents/pom.xml b/core/camel-allcomponents/pom.xml index 62183f5..dba0b8e 100644 --- a/core/camel-allcomponents/pom.xml +++ b/core/camel-allcomponents/pom.xml @@ -400,6 +400,10 @@ </dependency> <dependency> <groupId>org.apache.camel</groupId> + <artifactId>camel-datasonnet</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> <artifactId>camel-debezium-mongodb</artifactId> </dependency> <dependency> diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/BuilderSupport.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/BuilderSupport.java index ec041bb..fd25daa 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/builder/BuilderSupport.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/BuilderSupport.java @@ -26,6 +26,7 @@ import org.apache.camel.Expression; import org.apache.camel.NoSuchEndpointException; import org.apache.camel.RuntimeCamelException; import org.apache.camel.model.language.CSimpleExpression; +import org.apache.camel.model.language.DatasonnetExpression; import org.apache.camel.model.language.ExchangePropertyExpression; import org.apache.camel.model.language.HeaderExpression; import org.apache.camel.model.language.JoorExpression; @@ -150,6 +151,40 @@ public abstract class BuilderSupport { } /** + * Returns a Datasonnet expression value builder + */ + public ValueBuilder datasonnet(String value) { + DatasonnetExpression exp = new DatasonnetExpression(value); + return new ValueBuilder(exp); + } + + /** + * Returns a Datasonnet expression value builder + */ + public ValueBuilder datasonnet(Expression value) { + DatasonnetExpression exp = new DatasonnetExpression(value); + return new ValueBuilder(exp); + } + + /** + * Returns a Datasonnet expression value builder + */ + public ValueBuilder datasonnet(String value, Class<?> resultType) { + DatasonnetExpression exp = new DatasonnetExpression(value); + exp.setResultType(resultType); + return new ValueBuilder(exp); + } + + /** + * Returns a Datasonnet expression value builder + */ + public ValueBuilder datasonnet(Expression value, Class<?> resultType) { + DatasonnetExpression exp = new DatasonnetExpression(value); + exp.setResultType(resultType); + return new ValueBuilder(exp); + } + + /** * Returns a simple expression value builder */ public SimpleBuilder simple(String value) { diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java index 0dacb5e..b8bdbc5 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java @@ -344,6 +344,16 @@ public class ExpressionClause<T> implements Expression, Predicate { } /** + * Evaluates a <a href="http://camel.apache.org/datasonnet.html">Datasonnet expression</a> + * + * @param text the expression to be evaluated + * @return the builder to continue processing the DSL + */ + public T datasonnet(String text) { + return delegate.datasonnet(text); + } + + /** * Evaluates a <a href="http://camel.apache.org/jsonpath.html">Json Path expression</a> * * @param text the expression to be evaluated diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java index b496134..2c84ab2 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java @@ -24,6 +24,7 @@ import org.apache.camel.Expression; import org.apache.camel.ExpressionFactory; import org.apache.camel.model.language.CSimpleExpression; import org.apache.camel.model.language.ConstantExpression; +import org.apache.camel.model.language.DatasonnetExpression; import org.apache.camel.model.language.ExchangePropertyExpression; import org.apache.camel.model.language.GroovyExpression; import org.apache.camel.model.language.HeaderExpression; @@ -326,6 +327,16 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware { } /** + * Evaluates a <a href="http://camel.apache.org/datasonnet.html">Datasonnet expression</a> + * + * @param text the expression to be evaluated + * @return the builder to continue processing the DSL + */ + public T datasonnet(String text) { + return expression(new DatasonnetExpression(text)); + } + + /** * Evaluates a <a href="http://camel.apache.org/jsonpath.html">Json Path expression</a> * * @param text the expression to be evaluated @@ -803,7 +814,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware { /** * Evaluates an XML token expression on the message body with XML content - * + * * @param path the xpath like path notation specifying the child nodes to tokenize * @param mode one of 'i', 'w', or 'u' to inject the namespaces to the token, to wrap the token with its * ancestor contet, or to unwrap to its element child @@ -836,7 +847,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware { /** * Evaluates an <a href="http://camel.apache.org/xpath.html">XPath expression</a> on the supplied header name's * contents - * + * * @param text the expression to be evaluated * @param headerName the name of the header to apply the expression to * @return the builder to continue processing the DSL @@ -965,7 +976,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware { /** * Evaluates an <a href="http://camel.apache.org/xquery.html">XQuery expression</a> - * + * * @param text the expression to be evaluated * @param headerName the name of the header to apply the expression to * @return the builder to continue processing the DSL diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/DatasonnetExpression.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/DatasonnetExpression.java new file mode 100644 index 0000000..a08d768 --- /dev/null +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/DatasonnetExpression.java @@ -0,0 +1,111 @@ +/* + * 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.model.language; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + +import org.apache.camel.Expression; +import org.apache.camel.spi.Metadata; + +/** + * To use DataSonnet scripts in Camel expressions or predicates. + */ +@Metadata(firstVersion = "3.7.0", label = "language,script", title = "DataSonnet") +@XmlRootElement(name = "datasonnet") +@XmlAccessorType(XmlAccessType.FIELD) +public class DatasonnetExpression extends ExpressionDefinition { + + @XmlAttribute(name = "bodyMediaType") + private String bodyMediaType; + + @XmlAttribute(name = "outputMediaType") + private String outputMediaType; + + @XmlAttribute(name = "resultType") + private String resultTypeName; + + @XmlTransient + private Class<?> resultType; + + public DatasonnetExpression() { + } + + public DatasonnetExpression(String expression) { + super(expression); + } + + public DatasonnetExpression(Expression expression) { + super(expression); + } + + @Override + public String getLanguage() { + return "datasonnet"; + } + + public String getBodyMediaType() { + return bodyMediaType; + } + + /** + * The String representation of the message's body MediaType + */ + public void setBodyMediaType(String bodyMediaType) { + this.bodyMediaType = bodyMediaType; + } + + public String getOutputMediaType() { + return outputMediaType; + } + + /** + * The String representation of the MediaType to output + */ + public void setOutputMediaType(String outputMediaType) { + this.outputMediaType = outputMediaType; + } + + public Class<?> getResultType() { + return resultType; + } + + /** + * Sets the class of the result type (type from output). + * <p/> + * The default result type is com.datasonnet.document.Document + */ + public void setResultType(Class<?> resultType) { + this.resultType = resultType; + } + + public String getResultTypeName() { + return resultTypeName; + } + + /** + * Sets the class name of the result type (type from output) + * <p/> + * The default result type is com.datasonnet.document.Document + */ + public void setResultTypeName(String resultTypeName) { + this.resultTypeName = resultTypeName; + } +} diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/DatasonnetExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/DatasonnetExpressionReifier.java new file mode 100644 index 0000000..904ff16 --- /dev/null +++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/DatasonnetExpressionReifier.java @@ -0,0 +1,62 @@ +/* + * 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.reifier.language; + +import org.apache.camel.CamelContext; +import org.apache.camel.Expression; +import org.apache.camel.Predicate; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.model.language.DatasonnetExpression; +import org.apache.camel.model.language.ExpressionDefinition; +import org.apache.camel.spi.Language; + +public class DatasonnetExpressionReifier extends ExpressionReifier<DatasonnetExpression> { + + public DatasonnetExpressionReifier(CamelContext camelContext, ExpressionDefinition definition) { + super(camelContext, (DatasonnetExpression) definition); + } + + @Override + protected void configureLanguage(Language language) { + if (definition.getResultType() == null && definition.getResultTypeName() != null) { + try { + Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(definition.getResultTypeName()); + definition.setResultType(clazz); + } catch (ClassNotFoundException e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + } + } + + @Override + protected Expression createExpression(Language language, String exp) { + return language.createExpression(exp, createProperties()); + } + + @Override + protected Predicate createPredicate(Language language, String exp) { + return language.createPredicate(exp, createProperties()); + } + + private Object[] createProperties() { + Object[] properties = new Object[3]; + properties[0] = definition.getResultType(); + properties[1] = parseString(definition.getBodyMediaType()); + properties[2] = parseString(definition.getOutputMediaType()); + return properties; + } +} diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java index 5d1c918..dd775a1 100644 --- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java +++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java @@ -29,6 +29,7 @@ import org.apache.camel.Predicate; import org.apache.camel.model.ExpressionSubElementDefinition; import org.apache.camel.model.language.CSimpleExpression; import org.apache.camel.model.language.ConstantExpression; +import org.apache.camel.model.language.DatasonnetExpression; import org.apache.camel.model.language.ExchangePropertyExpression; import org.apache.camel.model.language.ExpressionDefinition; import org.apache.camel.model.language.GroovyExpression; @@ -111,6 +112,8 @@ public class ExpressionReifier<T extends ExpressionDefinition> extends AbstractR return new ExpressionReifier<>(camelContext, definition); } else if (definition instanceof CSimpleExpression) { return new CSimpleExpressionReifier(camelContext, definition); + } else if (definition instanceof DatasonnetExpression) { + return new DatasonnetExpressionReifier(camelContext, definition); } else if (definition instanceof ExchangePropertyExpression) { return new ExpressionReifier<>(camelContext, definition); } else if (definition instanceof GroovyExpression) { diff --git a/parent/pom.xml b/parent/pom.xml index 8cc4d48..ce287b6 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -105,6 +105,7 @@ <cglib-version>3.2.12</cglib-version> <chunk-templates-version>3.5.0</chunk-templates-version> <citrus-version>2.8.0</citrus-version> + <classgraph-version>4.8.52</classgraph-version> <cmis-version>1.1.0</cmis-version> <cometd-java-client-version>4.0.4</cometd-java-client-version> <cometd-java-server>4.0.4</cometd-java-server> @@ -142,6 +143,7 @@ <!-- cxf-xjc is not released as often --> <cxf-xjc-plugin-version>3.3.1</cxf-xjc-plugin-version> <cxf-xjc-utils-version>3.3.1</cxf-xjc-utils-version> + <datasonnet-mapper-version>2.1.1</datasonnet-mapper-version> <deltaspike-version>1.9.4</deltaspike-version> <depends-maven-plugin-version>1.4.0</depends-maven-plugin-version> <derby-version>10.14.2.0</derby-version> @@ -480,6 +482,7 @@ <rxjava2-version>2.2.20</rxjava2-version> <saxon-version>9.9.1-7</saxon-version> <scala-version>2.11.7</scala-version> + <scala-datasonnet-version>2.13.3</scala-datasonnet-version> <scribe-version>1.3.7</scribe-version> <servicemix-specs-version>2.9.0</servicemix-specs-version> <servlet-version-range>[3,4)</servlet-version-range> @@ -1242,6 +1245,11 @@ </dependency> <dependency> <groupId>org.apache.camel</groupId> + <artifactId>camel-datasonnet</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> <artifactId>camel-debezium-common</artifactId> <version>${project.version}</version> </dependency>