You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2016/12/23 12:14:23 UTC

[2/4] camel git commit: CAMEL-10646: jsonpath now supports POJOs if jackson is on the classpath

CAMEL-10646: jsonpath now supports POJOs if jackson is on the classpath


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/0a3efe21
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/0a3efe21
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/0a3efe21

Branch: refs/heads/master
Commit: 0a3efe214aa6bb6b6e6c304444595f0ae30c72b3
Parents: 2baea25
Author: Claus Ibsen <da...@apache.org>
Authored: Fri Dec 23 12:54:24 2016 +0100
Committer: Claus Ibsen <da...@apache.org>
Committed: Fri Dec 23 12:54:24 2016 +0100

----------------------------------------------------------------------
 components/camel-jsonpath/pom.xml               | 20 +++-
 .../apache/camel/jsonpath/JsonPathAdapter.java  | 33 +++++++
 .../apache/camel/jsonpath/JsonPathEngine.java   | 97 ++++++++++++++++++--
 .../jsonpath/jackson/JacksonJsonAdapter.java    | 64 +++++++++++++
 .../jsonpath/JsonPathPojoTransformTest.java     | 51 ++++++++++
 .../org/apache/camel/jsonpath/MyPojoType.java   | 39 ++++++++
 6 files changed, 295 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/0a3efe21/components/camel-jsonpath/pom.xml
----------------------------------------------------------------------
diff --git a/components/camel-jsonpath/pom.xml b/components/camel-jsonpath/pom.xml
index 499e99c..b20deb3 100644
--- a/components/camel-jsonpath/pom.xml
+++ b/components/camel-jsonpath/pom.xml
@@ -31,8 +31,16 @@
   <description>Camel JSON Path Language</description>
 
   <properties>
-    <camel.osgi.export.pkg>org.apache.camel.jsonpath.*</camel.osgi.export.pkg>
+    <camel.osgi.export.pkg>
+      org.apache.camel.jsonpath,
+      !org.apache.camel.jsonpath.jackson
+    </camel.osgi.export.pkg>
     <camel.osgi.export.service>org.apache.camel.spi.LanguageResolver;language=jsonpath</camel.osgi.export.service>
+    <camel.osgi.import>
+      com.fasterxml.jackson.databind;resolution:=optional,
+      com.fasterxml.jackson.module.jaxb;resolution:=optional,
+      *
+    </camel.osgi.import>
   </properties>
 
   <dependencies>
@@ -50,6 +58,16 @@
       <artifactId>json-smart</artifactId>
       <version>${json-smart-version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.module</groupId>
+      <artifactId>jackson-module-jaxb-annotations</artifactId>
+      <optional>true</optional>
+    </dependency>
 
     <!-- testing -->
     <dependency>

http://git-wip-us.apache.org/repos/asf/camel/blob/0a3efe21/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathAdapter.java
----------------------------------------------------------------------
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathAdapter.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathAdapter.java
new file mode 100644
index 0000000..41e0903
--- /dev/null
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathAdapter.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath;
+
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+
+public interface JsonPathAdapter {
+
+    /**
+     * Attempt to read/convert the message body into a {@link Map} type
+     *
+     * @param body the message body
+     * @param exchange the Camel exchange
+     * @return converted as {@link Map} or <tt>null</tt> if not possible
+     */
+    Map readValue(Object body, Exchange exchange);
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/0a3efe21/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
----------------------------------------------------------------------
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
index 14071d7..0816e30 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathEngine.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
@@ -30,22 +31,29 @@ import com.jayway.jsonpath.Configuration.Defaults;
 import com.jayway.jsonpath.JsonPath;
 import com.jayway.jsonpath.Option;
 import com.jayway.jsonpath.internal.DefaultsImpl;
-
+import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
 import org.apache.camel.Expression;
-import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.component.file.GenericFile;
+import org.apache.camel.util.ObjectHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static com.jayway.jsonpath.Option.ALWAYS_RETURN_LIST;
+import static com.jayway.jsonpath.Option.SUPPRESS_EXCEPTIONS;
+
 public class JsonPathEngine {
 
     private static final Logger LOG = LoggerFactory.getLogger(JsonPathEngine.class);
 
+    private static final String JACKSON_JSON_ADAPTER = "org.apache.camel.jsonpath.jackson.JacksonJsonAdapter";
+
     private static final Pattern SIMPLE_PATTERN = Pattern.compile("\\$\\{[^\\}]+\\}", Pattern.MULTILINE);
     private final String expression;
     private final JsonPath path;
     private final Configuration configuration;
+    private JsonPathAdapter adapter;
+    private volatile boolean initJsonAdapter;
 
     public JsonPathEngine(String expression) {
         this(expression, false, true, null);
@@ -58,13 +66,13 @@ public class JsonPathEngine {
         if (options != null) {
             Configuration.ConfigurationBuilder builder = Configuration.builder().jsonProvider(defaults.jsonProvider()).options(options);
             if (suppressExceptions) {
-                builder.options(Option.SUPPRESS_EXCEPTIONS);
+                builder.options(SUPPRESS_EXCEPTIONS);
             }
             this.configuration = builder.build();
         } else {
             Configuration.ConfigurationBuilder builder = Configuration.builder().jsonProvider(defaults.jsonProvider());
             if (suppressExceptions) {
-                builder.options(Option.SUPPRESS_EXCEPTIONS);
+                builder.options(SUPPRESS_EXCEPTIONS);
             }
             this.configuration = builder.build();
         }
@@ -85,7 +93,7 @@ public class JsonPathEngine {
         }
     }
 
-    public Object read(Exchange exchange) throws IOException, InvalidPayloadException {
+    public Object read(Exchange exchange) throws Exception {
         if (path == null) {
             Expression exp = exchange.getContext().resolveLanguage("simple").createExpression(expression);
             String text = exp.evaluate(exchange, String.class);
@@ -97,10 +105,13 @@ public class JsonPathEngine {
         }
     }
 
-    private Object doRead(JsonPath path, Exchange exchange) throws IOException, InvalidPayloadException {
+    private Object doRead(JsonPath path, Exchange exchange) throws IOException, CamelExchangeException {
         Object json = exchange.getIn().getBody();
 
-        if (json instanceof GenericFile) {
+        if (json instanceof InputStream) {
+            return readWithInputStream(path, exchange);
+        } else if (json instanceof GenericFile) {
+            LOG.trace("JSonPath: {} is read as generic file from message body: {}", path, json);
             GenericFile<?> genericFile = (GenericFile<?>) json;
             if (genericFile.getCharset() != null) {
                 // special treatment for generic file with charset
@@ -110,16 +121,48 @@ public class JsonPathEngine {
         }
 
         if (json instanceof String) {
+            LOG.trace("JSonPath: {} is read as String from message body: {}", path, json);
             String str = (String) json;
             return path.read(str, configuration);
         } else if (json instanceof Map) {
+            LOG.trace("JSonPath: {} is read as Map from message body: {}", path, json);
             Map map = (Map) json;
             return path.read(map, configuration);
         } else if (json instanceof List) {
+            LOG.trace("JSonPath: {} is read as List from message body: {}", path, json);
             List list = (List) json;
             return path.read(list, configuration);
         } else {
-            InputStream is = exchange.getIn().getMandatoryBody(InputStream.class);
+            // can we find an adapter which can read the message body
+            Object answer = readWithAdapter(path, exchange);
+            if (answer == null) {
+                // fallback and attempt input stream for any other types
+                answer = readWithInputStream(path, exchange);
+            }
+            if (answer != null) {
+                return answer;
+            }
+        }
+
+        // is json path configured to suppress exceptions
+        if (configuration.getOptions().contains(SUPPRESS_EXCEPTIONS)) {
+            if (configuration.getOptions().contains(ALWAYS_RETURN_LIST)) {
+                return Collections.emptyList();
+            } else {
+                return null;
+            }
+        }
+
+        // okay it was not then lets throw a failure
+        throw new CamelExchangeException("Cannot read message body as supported JSon value", exchange);
+    }
+
+    private Object readWithInputStream(JsonPath path, Exchange exchange) throws IOException {
+        Object json = exchange.getIn().getBody();
+        LOG.trace("JSonPath: {} is read as InputStream from message body: {}", path, json);
+
+        InputStream is = exchange.getContext().getTypeConverter().tryConvertTo(InputStream.class, exchange, json);
+        if (is != null) {
             String jsonEncoding = exchange.getIn().getHeader(JsonPathConstants.HEADER_JSON_ENCODING, String.class);
             if (jsonEncoding != null) {
                 // json encoding specified in header
@@ -131,5 +174,43 @@ public class JsonPathEngine {
                 return path.read(jsonStream, jsonStream.getEncoding().name(), configuration);
             }
         }
+
+        return null;
+    }
+
+    private Object readWithAdapter(JsonPath path, Exchange exchange) {
+        Object json = exchange.getIn().getBody();
+        LOG.trace("JSonPath: {} is read with adapter from message body: {}", path, json);
+
+        if (!initJsonAdapter) {
+            try {
+                // need to load this adapter dynamically as its optional
+                LOG.debug("Attempting to enable JacksonJsonAdapter by resolving: {} from classpath", JACKSON_JSON_ADAPTER);
+                Class<?> clazz = exchange.getContext().getClassResolver().resolveClass(JACKSON_JSON_ADAPTER);
+                if (clazz != null) {
+                    Object obj = exchange.getContext().getInjector().newInstance(clazz);
+                    if (obj instanceof JsonPathAdapter) {
+                        adapter = (JsonPathAdapter) obj;
+                        LOG.debug("JacksonJsonAdapter found on classpath and enabled for camel-jsonpath: {}", adapter);
+                    }
+                }
+            } catch (Throwable e) {
+                // ignore
+            }
+            initJsonAdapter = true;
+        }
+
+        if (adapter != null) {
+            LOG.trace("Attempting to use JacksonJsonAdapter: {}", adapter);
+            Map map = adapter.readValue(json, exchange);
+            if (map != null) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("JacksonJsonAdapter converted message body from: {} to: java.util.Map", ObjectHelper.classCanonicalName(json));
+                }
+                return path.read(map, configuration);
+            }
+        }
+
+        return null;
     }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/0a3efe21/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/jackson/JacksonJsonAdapter.java
----------------------------------------------------------------------
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/jackson/JacksonJsonAdapter.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/jackson/JacksonJsonAdapter.java
new file mode 100644
index 0000000..f109628
--- /dev/null
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/jackson/JacksonJsonAdapter.java
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath.jackson;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
+import org.apache.camel.Exchange;
+import org.apache.camel.jsonpath.JsonPathAdapter;
+import org.apache.camel.spi.Registry;
+
+/**
+ * A Jackson {@link JsonPathAdapter} which is using Jackson to convert the message
+ * body to {@link Map}. This allows us to support POJO classes with camel-jsonpath.
+ */
+public class JacksonJsonAdapter implements JsonPathAdapter {
+
+    private final ObjectMapper defaultMapper;
+
+    public JacksonJsonAdapter() {
+        defaultMapper = new ObjectMapper();
+        // Enables JAXB processing so we can easily convert JAXB annotated pojos also
+        JaxbAnnotationModule module = new JaxbAnnotationModule();
+        defaultMapper.registerModule(module);
+    }
+
+    @Override
+    public Map readValue(Object body, Exchange exchange) {
+        ObjectMapper mapper = resolveObjectMapper(exchange.getContext().getRegistry());
+        try {
+            return mapper.convertValue(body, Map.class);
+        } catch (Throwable e) {
+            // ignore because we are attempting to convert
+        }
+
+        return null;
+    }
+
+    private ObjectMapper resolveObjectMapper(Registry registry) {
+        Set<ObjectMapper> mappers = registry.findByType(ObjectMapper.class);
+        if (mappers.size() == 1) {
+            return mappers.iterator().next();
+        }
+        return defaultMapper;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/camel/blob/0a3efe21/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathPojoTransformTest.java
----------------------------------------------------------------------
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathPojoTransformTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathPojoTransformTest.java
new file mode 100644
index 0000000..209bff2
--- /dev/null
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathPojoTransformTest.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.junit.Test;
+
+public class JsonPathPojoTransformTest extends CamelTestSupport {
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                    .transform().jsonpath("$.type")
+                    .to("mock:type");
+            }
+        };
+    }
+
+    @Test
+    public void testPojo() throws Exception {
+        getMockEndpoint("mock:type").expectedBodiesReceived("customer");
+
+        // this will be read using jackson if its on the classpath
+        MyPojoType pojo = new MyPojoType();
+        pojo.setKind("full");
+        pojo.setType("customer");
+
+        template.sendBody("direct:start", pojo);
+
+        assertMockEndpointsSatisfied();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/0a3efe21/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/MyPojoType.java
----------------------------------------------------------------------
diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/MyPojoType.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/MyPojoType.java
new file mode 100644
index 0000000..243e648
--- /dev/null
+++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/MyPojoType.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.jsonpath;
+
+public class MyPojoType {
+
+    private String kind;
+    private String type;
+
+    public String getKind() {
+        return kind;
+    }
+
+    public void setKind(String kind) {
+        this.kind = kind;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}