This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 24fb76f2e74152ca7da3c3a88e974486f69e7f2a Author: Jim MacInnes <[email protected]> AuthorDate: Thu Jul 9 19:08:20 2020 -0700 For Jira - CAMEL-15257 Initial implmentation of: camel jsonata integration --- components/camel-jsonata/pom.xml | 75 ++++++++++++ .../jsonata/JsonataComponentConfigurer.java | 50 ++++++++ .../jsonata/JsonataEndpointConfigurer.java | 73 +++++++++++ .../services/org/apache/camel/component.properties | 7 ++ .../services/org/apache/camel/component/jsonata | 2 + .../org/apache/camel/configurer/jsonata-component | 2 + .../org/apache/camel/configurer/jsonata-endpoint | 2 + .../apache/camel/component/jsonata/jsonata.json | 37 ++++++ .../src/main/docs/jsonata-component.adoc | 124 +++++++++++++++++++ .../camel/component/jsonata/JsonataComponent.java | 48 ++++++++ .../camel/component/jsonata/JsonataConstants.java | 31 +++++ .../camel/component/jsonata/JsonataEndpoint.java | 135 +++++++++++++++++++++ .../component/jsonata/JsonataInputOutputType.java | 21 ++++ .../component/jolt/JsonataFirstSampleTest.java | 74 +++++++++++ .../src/test/resources/log4j2.properties | 28 +++++ .../component/jsonata/firstSample/expressions.json | 5 + .../camel/component/jsonata/firstSample/input.json | 53 ++++++++ .../component/jsonata/firstSample/output.json | 1 + 18 files changed, 768 insertions(+) diff --git a/components/camel-jsonata/pom.xml b/components/camel-jsonata/pom.xml new file mode 100644 index 0000000..4123334 --- /dev/null +++ b/components/camel-jsonata/pom.xml @@ -0,0 +1,75 @@ +<?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/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <artifactId>components</artifactId> + <groupId>org.apache.camel</groupId> + <version>3.5.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-jsonata</artifactId> + <packaging>jar</packaging> + <name>Camel :: JSONATA</name> + <description>Camel JSONATA Support</description> + + <properties> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-support</artifactId> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + + <!-- https://mvnrepository.com/artifact/com.ibm.jsonata4java/JSONata4Java --> + <dependency> + <groupId>com.ibm.jsonata4java</groupId> + <artifactId>JSONata4Java</artifactId> + <version>1.0.0</version> + </dependency> + + <!-- logging --> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-junit5</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/components/camel-jsonata/src/generated/java/org/apache/camel/component/jsonata/JsonataComponentConfigurer.java b/components/camel-jsonata/src/generated/java/org/apache/camel/component/jsonata/JsonataComponentConfigurer.java new file mode 100644 index 0000000..0821cb7 --- /dev/null +++ b/components/camel-jsonata/src/generated/java/org/apache/camel/component/jsonata/JsonataComponentConfigurer.java @@ -0,0 +1,50 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.jsonata; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class JsonataComponentConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + JsonataComponent target = (JsonataComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "basicpropertybinding": + case "basicPropertyBinding": target.setBasicPropertyBinding(property(camelContext, boolean.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + default: return false; + } + } + + @Override + public Map<String, Object> getAllOptions(Object target) { + Map<String, Object> answer = new CaseInsensitiveMap(); + answer.put("basicPropertyBinding", boolean.class); + answer.put("lazyStartProducer", boolean.class); + return answer; + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + JsonataComponent target = (JsonataComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "basicpropertybinding": + case "basicPropertyBinding": return target.isBasicPropertyBinding(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + default: return null; + } + } +} + diff --git a/components/camel-jsonata/src/generated/java/org/apache/camel/component/jsonata/JsonataEndpointConfigurer.java b/components/camel-jsonata/src/generated/java/org/apache/camel/component/jsonata/JsonataEndpointConfigurer.java new file mode 100644 index 0000000..ca1638e --- /dev/null +++ b/components/camel-jsonata/src/generated/java/org/apache/camel/component/jsonata/JsonataEndpointConfigurer.java @@ -0,0 +1,73 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.jsonata; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.support.component.PropertyConfigurerSupport; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class JsonataEndpointConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + JsonataEndpoint target = (JsonataEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "allowcontextmapall": + case "allowContextMapAll": target.setAllowContextMapAll(property(camelContext, boolean.class, value)); return true; + case "basicpropertybinding": + case "basicPropertyBinding": target.setBasicPropertyBinding(property(camelContext, boolean.class, value)); return true; + case "contentcache": + case "contentCache": target.setContentCache(property(camelContext, boolean.class, value)); return true; + case "inputtype": + case "inputType": target.setInputType(property(camelContext, org.apache.camel.component.jsonata.JsonataInputOutputType.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + case "outputtype": + case "outputType": target.setOutputType(property(camelContext, org.apache.camel.component.jsonata.JsonataInputOutputType.class, value)); return true; + case "synchronous": target.setSynchronous(property(camelContext, boolean.class, value)); return true; + default: return false; + } + } + + @Override + public Map<String, Object> getAllOptions(Object target) { + Map<String, Object> answer = new CaseInsensitiveMap(); + answer.put("allowContextMapAll", boolean.class); + answer.put("basicPropertyBinding", boolean.class); + answer.put("contentCache", boolean.class); + answer.put("inputType", org.apache.camel.component.jsonata.JsonataInputOutputType.class); + answer.put("lazyStartProducer", boolean.class); + answer.put("outputType", org.apache.camel.component.jsonata.JsonataInputOutputType.class); + answer.put("synchronous", boolean.class); + return answer; + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + JsonataEndpoint target = (JsonataEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "allowcontextmapall": + case "allowContextMapAll": return target.isAllowContextMapAll(); + case "basicpropertybinding": + case "basicPropertyBinding": return target.isBasicPropertyBinding(); + case "contentcache": + case "contentCache": return target.isContentCache(); + case "inputtype": + case "inputType": return target.getInputType(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + case "outputtype": + case "outputType": return target.getOutputType(); + case "synchronous": return target.isSynchronous(); + default: return null; + } + } +} + diff --git a/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/component.properties new file mode 100644 index 0000000..84420b5 --- /dev/null +++ b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/component.properties @@ -0,0 +1,7 @@ +# Generated by camel build tools - do NOT edit this file! +components=jsonata +groupId=org.apache.camel +artifactId=camel-jsonata +version=3.5.0-SNAPSHOT +projectName=Camel :: JSONATA +projectDescription=Camel JSONATA Support diff --git a/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/component/jsonata b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/component/jsonata new file mode 100644 index 0000000..f1fbb7a --- /dev/null +++ b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/component/jsonata @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jsonata.JsonataComponent diff --git a/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/configurer/jsonata-component b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/configurer/jsonata-component new file mode 100644 index 0000000..0caebfd --- /dev/null +++ b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/configurer/jsonata-component @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jsonata.JsonataComponentConfigurer diff --git a/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/configurer/jsonata-endpoint b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/configurer/jsonata-endpoint new file mode 100644 index 0000000..72de606 --- /dev/null +++ b/components/camel-jsonata/src/generated/resources/META-INF/services/org/apache/camel/configurer/jsonata-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.jsonata.JsonataEndpointConfigurer diff --git a/components/camel-jsonata/src/generated/resources/org/apache/camel/component/jsonata/jsonata.json b/components/camel-jsonata/src/generated/resources/org/apache/camel/component/jsonata/jsonata.json new file mode 100644 index 0000000..cd2656c --- /dev/null +++ b/components/camel-jsonata/src/generated/resources/org/apache/camel/component/jsonata/jsonata.json @@ -0,0 +1,37 @@ +{ + "component": { + "kind": "component", + "name": "jsonata", + "title": "JSONATA", + "description": "JSON to JSON transformation using JSONATA.", + "deprecated": false, + "firstVersion": "3.4.0", + "label": "transformation", + "javaType": "org.apache.camel.component.jsonata.JsonataComponent", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-jsonata", + "version": "3.5.0-SNAPSHOT", + "scheme": "jsonata", + "extendsScheme": "", + "syntax": "jsonata:resourceUri", + "async": false, + "consumerOnly": false, + "producerOnly": true, + "lenientProperties": false + }, + "componentProperties": { + "lazyStartProducer": { "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the r [...] + "basicPropertyBinding": { "kind": "property", "displayName": "Basic Property Binding", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the component should use basic property binding (Camel 2.x) or the newer property binding with additional capabilities" } + }, + "properties": { + "resourceUri": { "kind": "path", "displayName": "Resource Uri", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "secret": false, "description": "Path to the resource. You can prefix with: classpath, file, http, ref, or bean. classpath, file and http loads the resource using these protocols (classpath is default). ref will lookup the resource in the registry. bean will call a method on a [...] + "allowContextMapAll": { "kind": "parameter", "displayName": "Allow Context Map All", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": "false", "description": "Sets whether the context map should allow access to all details. By default only the message body and headers can be accessed. This option can be enabled for full access to the current Exchange and CamelContext. Doing so impose a [...] + "contentCache": { "kind": "parameter", "displayName": "Content Cache", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": "false", "description": "Sets whether to use resource content cache or not" }, + "inputType": { "kind": "parameter", "displayName": "Input Type", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.jsonata.JsonataInputOutputType", "enum": [ "Jackson", "JsonString" ], "deprecated": false, "secret": false, "defaultValue": "Jackson", "description": "Specifies if the input is hydrated JSON or a JSON String." }, + "lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the [...] + "outputType": { "kind": "parameter", "displayName": "Output Type", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.jsonata.JsonataInputOutputType", "enum": [ "Jackson", "JsonString" ], "deprecated": false, "secret": false, "defaultValue": "Jackson", "description": "Specifies if the output should be hydrated JSON or a JSON String." }, + "basicPropertyBinding": { "kind": "parameter", "displayName": "Basic Property Binding", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the endpoint should use basic property binding (Camel 2.x) or the newer property binding with additional capabilities" }, + "synchronous": { "kind": "parameter", "displayName": "Synchronous", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": "false", "description": "Sets whether synchronous processing should be strictly used, or Camel is allowed to use asynchronous processing (if supported)." } + } +} diff --git a/components/camel-jsonata/src/main/docs/jsonata-component.adoc b/components/camel-jsonata/src/main/docs/jsonata-component.adoc new file mode 100644 index 0000000..f0f88fc --- /dev/null +++ b/components/camel-jsonata/src/main/docs/jsonata-component.adoc @@ -0,0 +1,124 @@ +[[jsonata-component]] += JSONATA Component +:docTitle: JSONATA +:artifactId: camel-jsonata +:description: JSON to JSON transformation using JSONATA. +:since: 3.4 +:supportLevel: Preview +:component-header: Only producer is supported + +*Since Camel {since}* + +*{component-header}* + +The Jsonata component allows you to process JSON messages using the +https://jsonata.org/[JSONATA] specification. This can be +ideal when doing JSON to JSON transformation and other transformations from JSON. + +Maven users will need to add the following dependency to +their `pom.xml` for this component: + +[source,xml] +------------------------------------------------------------ +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jsonata</artifactId> + <version>x.x.x</version> + <!-- use the same version as your Camel core version --> +</dependency> +------------------------------------------------------------ + + +== URI format + +[source,java] +----------------------- +jsonata:specName[?options] +----------------------- + +Where *specName* is the classpath-local URI of the specification to +invoke; or the complete URL of the remote specification +(eg: \file://folder/myfile.vm). + +You can append query options to the URI in the following +format, `?option=value&option=value&...` + +== Options + + + + +// component options: START +The JSONATA component supports 2 options, which are listed below. + + + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *lazyStartProducer* (producer) | Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and [...] +| *basicPropertyBinding* (advanced) | Whether the component should use basic property binding (Camel 2.x) or the newer property binding with additional capabilities | false | boolean +|=== +// component options: END + + +// endpoint options: START +The JSONATA endpoint is configured using URI syntax: + +---- +jsonata:resourceUri +---- + +with the following path and query parameters: + +=== Path Parameters (1 parameters): + + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *resourceUri* | *Required* Path to the resource. You can prefix with: classpath, file, http, ref, or bean. classpath, file and http loads the resource using these protocols (classpath is default). ref will lookup the resource in the registry. bean will call a method on a bean to be used as the resource. For bean you can specify the method name after dot, eg bean:myBean.myMethod. | | String +|=== + + +=== Query Parameters (7 parameters): + + +[width="100%",cols="2,5,^1,2",options="header"] +|=== +| Name | Description | Default | Type +| *allowContextMapAll* (producer) | Sets whether the context map should allow access to all details. By default only the message body and headers can be accessed. This option can be enabled for full access to the current Exchange and CamelContext. Doing so impose a potential security risk as this opens access to the full power of CamelContext API. | false | boolean +| *contentCache* (producer) | Sets whether to use resource content cache or not | false | boolean +| *inputType* (producer) | Specifies if the input is hydrated JSON or a JSON String. The value can be one of: Jackson, JsonString | Jackson | JsonataInputOutputType +| *lazyStartProducer* (producer) | Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and [...] +| *outputType* (producer) | Specifies if the output should be hydrated JSON or a JSON String. The value can be one of: Jackson, JsonString | Jackson | JsonataInputOutputType +| *basicPropertyBinding* (advanced) | Whether the endpoint should use basic property binding (Camel 2.x) or the newer property binding with additional capabilities | false | boolean +| *synchronous* (advanced) | Sets whether synchronous processing should be strictly used, or Camel is allowed to use asynchronous processing (if supported). | false | boolean +|=== +// endpoint options: END + + + + +== Samples + +For example you could use something like + +[source,java] +-------------------------------------- +from("activemq:My.Queue"). + to("jsonata:com/acme/MyResponse.json"); +-------------------------------------- + +And a file based resource: + +[source,java] +--------------------------------------------------------------- +from("activemq:My.Queue"). + to("jsonata:file://myfolder/MyResponse.json?contentCache=true"). + to("activemq:Another.Queue"); +--------------------------------------------------------------- + + + +include::camel-spring-boot::page$jsonata-component.adoc[] diff --git a/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataComponent.java b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataComponent.java new file mode 100644 index 0000000..3a93748 --- /dev/null +++ b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataComponent.java @@ -0,0 +1,48 @@ +/* + * 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.component.jsonata; + +import java.util.Map; + +import org.apache.camel.Endpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.annotations.Component; +import org.apache.camel.support.DefaultComponent; +import org.apache.camel.support.ResourceHelper; + +@Component("jsonata") +public class JsonataComponent extends DefaultComponent { + + public JsonataComponent() { + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + boolean cache = getAndRemoveParameter(parameters, "contentCache", Boolean.class, Boolean.TRUE); + + JsonataEndpoint answer = new JsonataEndpoint(uri, this, remaining); + answer.setContentCache(cache); + + // if its a http resource then append any remaining parameters and update the resource uri + if (ResourceHelper.isHttpUri(remaining)) { + remaining = ResourceHelper.appendParameters(remaining, parameters); + answer.setResourceUri(remaining); + } + + return answer; + } +} diff --git a/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataConstants.java b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataConstants.java new file mode 100644 index 0000000..07547d7c --- /dev/null +++ b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataConstants.java @@ -0,0 +1,31 @@ +/* + * 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.component.jsonata; + +/** + * Jsonata Constants. + */ +public final class JsonataConstants { + + public static final String Jsonata_RESOURCE_URI = "CamelJsonataResourceUri"; + + public static final String Jsonata_CONTEXT = "CamelJsonataContext"; + + private JsonataConstants() { + // Utility class + } +} diff --git a/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataEndpoint.java b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataEndpoint.java new file mode 100644 index 0000000..8aca0d6 --- /dev/null +++ b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataEndpoint.java @@ -0,0 +1,135 @@ +/* + * 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.component.jsonata; + +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; +import java.util.Map; +import org.apache.camel.Category; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; +import org.apache.camel.Message; +import org.apache.camel.component.ResourceEndpoint; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.util.ObjectHelper; +import com.api.jsonata4java.expressions.EvaluateException; +import com.api.jsonata4java.expressions.EvaluateRuntimeException; +import com.api.jsonata4java.expressions.Expressions; +import com.api.jsonata4java.expressions.ParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * JSON to JSON transformation using JSONATA. + */ +@UriEndpoint(firstVersion = "3.4.0", scheme = "jsonata", title = "JSONATA", syntax = "jsonata:resourceUri", producerOnly = true, category = {Category.TRANSFORMATION}) +public class JsonataEndpoint extends ResourceEndpoint { + + private Expressions expressions = null; + + @UriParam(defaultValue = "Jackson") + private JsonataInputOutputType outputType; + + @UriParam(defaultValue = "Jackson") + private JsonataInputOutputType inputType; + + public JsonataEndpoint() { + } + + public JsonataEndpoint(String uri, JsonataComponent component, String resourceUri) { + super(uri, component, resourceUri); + } + + @Override + public ExchangePattern getExchangePattern() { + return ExchangePattern.InOut; + } + + @Override + protected String createEndpointUri() { + return "Jsonata:" + getResourceUri(); + } + + public JsonataInputOutputType getOutputType() { + return outputType; + } + + /** + * Specifies if the output should be hydrated JSON or a JSON String. + */ + public void setOutputType(JsonataInputOutputType outputType) { + this.outputType = outputType; + } + + public JsonataInputOutputType getInputType() { + return inputType; + } + + /** + * Specifies if the input is hydrated JSON or a JSON String. + */ + public void setInputType(JsonataInputOutputType inputType) { + this.inputType = inputType; + } + + public JsonataEndpoint findOrCreateEndpoint(String uri, String newResourceUri) { + String newUri = uri.replace(getResourceUri(), newResourceUri); + log.debug("Getting endpoint with URI: {}", newUri); + return getCamelContext().getEndpoint(newUri, JsonataEndpoint.class); + } + + @Override + protected void onExchange(Exchange exchange) throws Exception { + String path = getResourceUri(); + ObjectHelper.notNull(path, "resourceUri"); + + JsonNode input; + ObjectMapper mapper = new ObjectMapper(); + if (getInputType() == JsonataInputOutputType.JsonString) { + input = mapper.readTree(exchange.getIn().getBody(InputStream.class)); + } else { + input = (JsonNode)exchange.getIn().getBody(); + } + + Map<String, Object> inputContextMap = null; + JsonNode output=null; + JsonNode jsonObj = null; + if(expressions == null) { + String spec = new BufferedReader( + new InputStreamReader(getResourceAsInputStream(), StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + expressions = Expressions.parse(spec); + } + output = expressions.evaluate(input); + + // now lets output the results to the exchange + Message out = exchange.getOut(); // getOut() is depricated + if (getOutputType() == JsonataInputOutputType.JsonString) { + out.setBody(output.toString()); + } else { + out.setBody(output); + } + out.setHeaders(exchange.getIn().getHeaders()); + } + +} diff --git a/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataInputOutputType.java b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataInputOutputType.java new file mode 100644 index 0000000..b5ec177 --- /dev/null +++ b/components/camel-jsonata/src/main/java/org/apache/camel/component/jsonata/JsonataInputOutputType.java @@ -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. + */ +package org.apache.camel.component.jsonata; + +public enum JsonataInputOutputType { + Jackson, JsonString +} diff --git a/components/camel-jsonata/src/test/java/org/apache/camel/component/jolt/JsonataFirstSampleTest.java b/components/camel-jsonata/src/test/java/org/apache/camel/component/jolt/JsonataFirstSampleTest.java new file mode 100644 index 0000000..aa0ec70 --- /dev/null +++ b/components/camel-jsonata/src/test/java/org/apache/camel/component/jolt/JsonataFirstSampleTest.java @@ -0,0 +1,74 @@ +/* + * 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.component.jsonata; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.util.IOHelper; +import org.junit.jupiter.api.Test; + +/** + * Unit test based on the first sample test from the Jsonata project. + */ +public class JsonataFirstSampleTest extends CamelTestSupport { + + @Test + public void testFirstSampleJsonata() throws Exception { + //getMockEndpoint("mock:result").expectedMinimumMessageCount(1); + getMockEndpoint("mock:result").expectedBodiesReceived( + IOHelper.loadText( + ResourceHelper.resolveMandatoryResourceAsInputStream( + context, "org/apache/camel/component/jsonata/firstSample/output.json") + ).trim() // Remove the last newline added by IOHelper.loadText() + ); + + sendBody("direct://start", + ResourceHelper.resolveMandatoryResourceAsInputStream( + context, "org/apache/camel/component/jsonata/firstSample/input.json")); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + final Processor processor = new Processor() { + public void process(Exchange exchange) { + Map<String, String> contextMap = new HashMap<>(); + contextMap.put("contextB", "bb"); + + exchange.getIn().setHeader(JsonataConstants.Jsonata_CONTEXT, contextMap); + } + }; + + return new RouteBuilder() { + public void configure() { + JsonataComponent Jsonata = context.getComponent("jsonata", JsonataComponent.class); + + from("direct://start") + .process(processor) + .to("jsonata:org/apache/camel/component/jsonata/firstSample/expressions.json?inputType=JsonString&outputType=JsonString") + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-jsonata/src/test/resources/log4j2.properties b/components/camel-jsonata/src/test/resources/log4j2.properties new file mode 100644 index 0000000..870e1d0 --- /dev/null +++ b/components/camel-jsonata/src/test/resources/log4j2.properties @@ -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. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-jsonata-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +appender.stdout.type = Console +appender.stdout.name = stdout +appender.stdout.layout.type = PatternLayout +appender.stdout.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file diff --git a/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/expressions.json b/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/expressions.json new file mode 100644 index 0000000..16afe3b --- /dev/null +++ b/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/expressions.json @@ -0,0 +1,5 @@ +{ + "name": FirstName & " " & Surname, + "mobile": Phone[type = "mobile"].number +} + diff --git a/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/input.json b/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/input.json new file mode 100644 index 0000000..bceae29 --- /dev/null +++ b/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/input.json @@ -0,0 +1,53 @@ +{ + "FirstName": "Fred", + "Surname": "Smith", + "Age": 28, + "Address": { + "Street": "Hursley Park", + "City": "Winchester", + "Postcode": "SO21 2JN" + }, + "Phone": [ + { + "type": "home", + "number": "0203 544 1234" + }, + { + "type": "office", + "number": "01962 001234" + }, + { + "type": "office", + "number": "01962 001235" + }, + { + "type": "mobile", + "number": "077 7700 1234" + } + ], + "Email": [ + { + "type": "office", + "address": [ + "[email protected]", + "[email protected]" + ] + }, + { + "type": "home", + "address": [ + "[email protected]", + "[email protected]" + ] + } + ], + "Other": { + "Over 18 ?": true, + "Misc": null, + "Alternative.Address": { + "Street": "Brick Lane", + "City": "London", + "Postcode": "E1 6RF" + } + } + } \ No newline at end of file diff --git a/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/output.json b/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/output.json new file mode 100644 index 0000000..1b22e71 --- /dev/null +++ b/components/camel-jsonata/src/test/resources/org/apache/camel/component/jsonata/firstSample/output.json @@ -0,0 +1 @@ +{"name":"Fred Smith","mobile":"077 7700 1234"} \ No newline at end of file
