You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2019/07/22 09:50:45 UTC

[sling-org-apache-sling-contentparser-xml] 01/06: SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations

This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-xml.git

commit 09e33ef939ce1428d97e73787e2f7f2362d5aa44
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Jul 12 16:33:23 2019 +0200

    SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations
    
    * extracted a test utils module, which can be used by all contentparser
    implementations
    * added a XML Content Parser implementation
---
 pom.xml                                            | 102 +++++++
 .../xml/internal/XmlContentParser.java             | 224 ++++++++++++++
 .../xml/internal/XmlContentParserTest.java         | 164 ++++++++++
 src/test/resources/content-test/content.xml        | 334 +++++++++++++++++++++
 .../invalid-test/contentWithObjectList.json        |  14 +
 src/test/resources/invalid-test/invalid.json       |   1 +
 6 files changed, 839 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d2063ac
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>org.apache.sling.contentparser.xml</artifactId>
+    <version>0.9.0-SNAPSHOT</version>
+
+    <name>Apache Sling Content Parser for XML</name>
+    <description>
+        Apache Sling Content Parser for resource trees stored in XML files
+    </description>
+
+    <scm>
+        <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-xml.git</connection>
+        <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-contentparser-xml.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-contentparser-xml.git</url>
+        <tag>HEAD</tag>
+    </scm>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.contentparser.api</artifactId>
+            <version>0.9.0-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.6</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.8</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.framework</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.contentparser.testutils</artifactId>
+            <version>0.9.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>15.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/contentparser/xml/internal/XmlContentParser.java b/src/main/java/org/apache/sling/contentparser/xml/internal/XmlContentParser.java
new file mode 100644
index 0000000..5c18539
--- /dev/null
+++ b/src/main/java/org/apache/sling/contentparser/xml/internal/XmlContentParser.java
@@ -0,0 +1,224 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.contentparser.xml.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.contentparser.api.ContentHandler;
+import org.apache.sling.contentparser.api.ContentParser;
+import org.apache.sling.contentparser.api.ParseException;
+import org.apache.sling.contentparser.api.ParserHelper;
+import org.apache.sling.contentparser.api.ParserOptions;
+import org.osgi.service.component.annotations.Component;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Parses XML files that contains content fragments.
+ * Instance of this class is thread-safe.
+ */
+@Component(
+        property = {
+                ContentParser.SERVICE_PROPERTY_CONTENT_TYPE + "=" + ContentParser.XML_CONTENT_TYPE
+        }
+)
+public final class XmlContentParser implements ContentParser {
+
+    private static final String JCR_PRIMARY_TYPE = "jcr:primaryType";
+    private final DocumentBuilderFactory documentBuilderFactory;
+
+    public XmlContentParser() {
+        documentBuilderFactory = DocumentBuilderFactory.newInstance();
+    }
+
+    @Override
+    public void parse(ContentHandler handler, InputStream is, ParserOptions parserOptions) throws IOException, ParseException {
+        try {
+            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+            Document doc = documentBuilder.parse(is);
+            parse(handler, doc.getDocumentElement(), parserOptions, null);
+        } catch (ParserConfigurationException | SAXException ex) {
+            throw new ParseException("Error parsing JCR XML content.", ex);
+        }
+    }
+
+    private void parse(ContentHandler handler, Element element, ParserOptions parserOptions, String parentPath) {
+        // build node path
+        String path;
+        if (parentPath == null) {
+            path = "/";
+        } else {
+            String name = getChildText(element, "name");
+            if (StringUtils.isEmpty(name)) {
+                throw new ParseException("Child node without name detected below path " + parentPath);
+            }
+            if (parserOptions.getIgnoreResourceNames().contains(name)) {
+                return;
+            }
+            path = parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name;
+        }
+
+        Map<String, Object> properties = new HashMap<>();
+
+        // primary node type and mixins
+        String primaryType = getChildText(element, "primaryNodeType");
+        if (StringUtils.isNotBlank(primaryType) && !parserOptions.getIgnorePropertyNames().contains("jcr:primaryType")) {
+            properties.put("jcr:primaryType", primaryType);
+        }
+        String[] mixins = getChildTextArray(element, "mixinNodeType");
+        if (mixins.length > 0 && !parserOptions.getIgnorePropertyNames().contains("jcr:mixinTypes")) {
+            properties.put("jcr:mixinTypes", mixins);
+        }
+
+        // properties
+        List<Element> propertyElements = getChildren(element, "property");
+        for (Element propertyElement : propertyElements) {
+
+            // property name
+            String name = getChildText(propertyElement, "name");
+            if (StringUtils.isBlank(name)) {
+                throw new ParseException("Property without name detected at path " + path);
+            }
+            if (parserOptions.getIgnorePropertyNames().contains(name)) {
+                continue;
+            }
+
+            // property type
+            String type = getChildText(propertyElement, "type");
+            if (StringUtils.isBlank(type)) {
+                throw new ParseException("Property '" + name + "' has no type at path " + path);
+            }
+
+            // property value
+            Object value;
+            List<Element> valuesElements = getChildren(propertyElement, "values");
+            if (!valuesElements.isEmpty()) {
+                Element valuesElement = valuesElements.get(0);
+                List<Element> valueElements = getChildren(valuesElement, "value");
+                String[] stringValues = new String[valueElements.size()];
+                for (int i = 0; i < valueElements.size(); i++) {
+                    stringValues[i] = valueElements.get(i).getTextContent();
+                }
+                value = convertMultiValue(stringValues, type);
+            } else {
+                String stringValue = getChildText(propertyElement, "value");
+                value = convertValue(stringValue, type);
+            }
+            properties.put(name, value);
+        }
+
+        String defaultPrimaryType = parserOptions.getDefaultPrimaryType();
+        if (defaultPrimaryType != null) {
+            if (!properties.containsKey(JCR_PRIMARY_TYPE)) {
+                properties.put(JCR_PRIMARY_TYPE, defaultPrimaryType);
+            }
+        }
+        handler.resource(path, properties);
+
+        // child nodes
+        List<Element> nodeElements = getChildren(element, "node");
+        for (Element node : nodeElements) {
+            parse(handler, node, parserOptions, path);
+        }
+
+    }
+
+    private List<Element> getChildren(Element element, String childName) {
+        List<Element> result = new ArrayList<>();
+        NodeList children = element.getChildNodes();
+        int len = children.getLength();
+        for (int i = 0; i < len; i++) {
+            Node child = children.item(i);
+            if (child instanceof Element) {
+                Element childElement = (Element) child;
+                if (StringUtils.equals(childElement.getNodeName(), childName)) {
+                    result.add(childElement);
+                }
+            }
+        }
+        return result;
+    }
+
+    private String getChildText(Element element, String childName) {
+        List<Element> children = getChildren(element, childName);
+        if (children.isEmpty()) {
+            return null;
+        } else if (children.size() == 1) {
+            return children.get(0).getTextContent();
+        } else {
+            throw new ParseException("Found multiple elements with name '" + childName + "': " + children.size());
+        }
+    }
+
+    private String[] getChildTextArray(Element element, String childName) {
+        List<Element> children = getChildren(element, childName);
+        String[] result = new String[children.size()];
+        for (int i = 0; i < children.size(); i++) {
+            result[i] = children.get(i).getTextContent();
+        }
+        return result;
+    }
+
+    private Object convertValue(String value, String type) {
+        switch (type) {
+            case "String":
+            case "Name":
+            case "Path":
+            case "Reference":
+            case "WeakReference":
+            case "URI":
+                return value;
+            case "Long":
+                return Long.valueOf(value);
+            case "Double":
+                return Double.valueOf(value);
+            case "Date":
+                return ParserHelper.parseDate(value);
+            case "Boolean":
+                return Boolean.valueOf(value);
+            case "Decimal":
+                return new BigDecimal(value);
+            default:
+                throw new ParseException(String.format("Unsupported property type: %s.", type));
+        }
+    }
+
+    private Object convertMultiValue(String[] values, String type) {
+        Object[] result = new Object[values.length];
+        for (int i = 0; i < values.length; i++) {
+            result[i] = convertValue(values[i], type);
+        }
+        return ParserHelper.convertSingleTypeArray(result);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/contentparser/xml/internal/XmlContentParserTest.java b/src/test/java/org/apache/sling/contentparser/xml/internal/XmlContentParserTest.java
new file mode 100644
index 0000000..111ac73
--- /dev/null
+++ b/src/test/java/org/apache/sling/contentparser/xml/internal/XmlContentParserTest.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.sling.contentparser.xml.internal;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.sling.contentparser.api.ContentParser;
+import org.apache.sling.contentparser.api.ParseException;
+import org.apache.sling.contentparser.api.ParserOptions;
+import org.apache.sling.contentparser.testutils.mapsupport.ContentElement;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.apache.sling.contentparser.testutils.TestUtils.parse;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class XmlContentParserTest {
+
+    private File file;
+    private ContentParser underTest;
+
+    @Before
+    public void setUp() {
+        file = new File("src/test/resources/content-test/content.xml");
+        underTest = new XmlContentParser();
+    }
+
+    @Test
+    public void testPageJcrPrimaryType() throws Exception {
+        ContentElement content = parse(underTest, file);
+
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+    }
+
+    @Test
+    public void testDataTypes() throws Exception {
+        ContentElement content = parse(underTest, file);
+
+        Map<String, Object> props = content.getChild("toolbar/profiles/jcr:content").getProperties();
+        assertEquals(true, props.get("hideInNav"));
+
+        assertEquals(1234567890123L, props.get("longProp"));
+        assertEquals(new BigDecimal("1.2345"), props.get("decimalProp"));
+        assertEquals(true, props.get("booleanProp"));
+
+        assertArrayEquals(new Long[]{1234567890123L, 55L}, (Long[]) props.get("longPropMulti"));
+        assertArrayEquals(new BigDecimal[]{new BigDecimal("1.2345"), new BigDecimal("1.1")}, (BigDecimal[]) props.get("decimalPropMulti"));
+        assertArrayEquals(new Boolean[]{true, false}, (Boolean[]) props.get("booleanPropMulti"));
+    }
+
+    @Test
+    public void testContentProperties() throws Exception {
+        ContentElement content = parse(underTest, file);
+
+        Map<String, Object> props = content.getChild("jcr:content/header").getProperties();
+        assertEquals("/content/dam/sample/header.png", props.get("imageReference"));
+    }
+
+    @Test
+    public void testCalendar() throws Exception {
+        ContentElement content = parse(underTest, new ParserOptions().detectCalendarValues(true), file);
+
+        Map<String, Object> props = content.getChild("jcr:content").getProperties();
+
+        Calendar calendar = (Calendar) props.get("app:lastModified");
+        assertNotNull(calendar);
+
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT+2"));
+
+        assertEquals(2014, calendar.get(Calendar.YEAR));
+        assertEquals(4, calendar.get(Calendar.MONTH) + 1);
+        assertEquals(22, calendar.get(Calendar.DAY_OF_MONTH));
+
+        assertEquals(15, calendar.get(Calendar.HOUR_OF_DAY));
+        assertEquals(11, calendar.get(Calendar.MINUTE));
+        assertEquals(24, calendar.get(Calendar.SECOND));
+    }
+
+    @Test
+    public void testUTF8Chars() throws Exception {
+
+        ContentElement content = parse(underTest, file);
+
+        Map<String, Object> props = content.getChild("jcr:content").getProperties();
+
+        assertEquals("äöü߀", props.get("utf8Property"));
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseInvalidJson() throws Exception {
+        file = new File("src/test/resources/invalid-test/invalid.json");
+
+        ContentElement content = parse(underTest, file);
+        assertNull(content);
+    }
+
+    @Test(expected = ParseException.class)
+    public void testParseInvalidJsonWithObjectList() throws Exception {
+        file = new File("src/test/resources/invalid-test/contentWithObjectList.json");
+
+        ContentElement content = parse(underTest, file);
+        assertNull(content);
+    }
+
+    @Test
+    public void testIgnoreResourcesProperties() throws Exception {
+        ContentElement content = parse(underTest,  new ParserOptions().ignoreResourceNames(ImmutableSet.of("header", "newslist"))
+                .ignorePropertyNames(ImmutableSet.of("jcr:title")), file);
+        ContentElement child = content.getChild("jcr:content");
+
+        assertEquals("Sample Homepage", child.getProperties().get("pageTitle"));
+        assertNull(child.getProperties().get("jcr:title"));
+
+        assertNull(child.getChildren().get("header"));
+        assertNull(child.getChildren().get("newslist"));
+        assertNotNull(child.getChildren().get("lead"));
+
+        assertEquals("abc", child.getProperties().get("refpro1"));
+        assertEquals("def", child.getProperties().get("pathprop1"));
+    }
+
+    @Test
+    public void testGetChild() throws Exception {
+
+        ContentElement content = parse(underTest, file);
+        assertNull(content.getName());
+
+        ContentElement deepChild = content.getChild("jcr:content/par/image/file/jcr:content");
+        assertEquals("jcr:content", deepChild.getName());
+        assertEquals("nt:resource", deepChild.getProperties().get("jcr:primaryType"));
+
+        ContentElement invalidChild = content.getChild("non/existing/path");
+        assertNull(invalidChild);
+
+        invalidChild = content.getChild("/jcr:content");
+        assertNull(invalidChild);
+    }
+
+}
diff --git a/src/test/resources/content-test/content.xml b/src/test/resources/content-test/content.xml
new file mode 100644
index 0000000..6caed95
--- /dev/null
+++ b/src/test/resources/content-test/content.xml
@@ -0,0 +1,334 @@
+<?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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<node>
+  <primaryNodeType>app:Page</primaryNodeType>
+  <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+  <property><name>jcr:created</name><value>Thu Aug 07 2014 16:32:59 GMT+0200</value><type>Date</type></property>
+  
+  <node>
+    <name>jcr:content</name>
+    <primaryNodeType>app:PageContent</primaryNodeType>
+    <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+    <property><name>jcr:title</name><value>English</value><type>String</type></property>
+    <property><name>app:template</name><value>sample/templates/homepage</value><type>String</type></property>
+    <property><name>jcr:created</name><value>Thu Aug 07 2014 16:32:59 GMT+0200</value><type>Date</type></property>
+    <property><name>app:lastModified</name><value>Tue Apr 22 2014 15:11:24 GMT+0200</value><type>Date</type></property>
+    <property><name>dateISO8601String</name><value>2014-04-22T15:11:24.000+02:00</value><type>Date</type></property>
+    <property><name>pageTitle</name><value>Sample Homepage</value><type>String</type></property>
+    <property><name>sling:resourceType</name><value>sample/components/homepage</value><type>String</type></property>
+    <property><name>sling:resourceSuperType</name><value>sample/components/supertype</value><type>String</type></property>
+    <property><name>app:designPath</name><value>/etc/designs/sample</value><type>String</type></property>
+    <property><name>app:lastModifiedBy</name><value>admin</value><type>String</type></property>
+    <property><name>utf8Property</name><value>äöü߀</value><type>String</type></property>
+    <property><name>refpro1</name><value>abc</value><type>Reference</type></property>
+    <property><name>pathprop1</name><value>def</value><type>Path</type></property>
+    
+    <node>
+      <name>par</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>sling:resourceType</name><value>foundation/components/parsys</value><type>String</type></property>
+      <node>
+        <name>colctrl</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>layout</name><value>2;colctrl-lt0</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Mon Aug 23 2010 22:02:24 GMT+0200</value><type>Date</type></property>
+        <property><name>jcr:lastModified</name><value>Mon Aug 23 2010 22:02:35 GMT+0200</value><type>Date</type></property>
+        <property><name>sling:resourceType</name><value>foundation/components/parsys/colctrl</value><type>String</type></property>
+      </node>
+      <node>
+        <name>image</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>fileReference</name><value>/content/dam/sample/portraits/jane_doe.jpg</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Mon Aug 23 2010 22:03:39 GMT+0200</value><type>Date</type></property>
+        <property><name>width</name><value>340</value><type>String</type></property>
+        <property><name>jcr:lastModified</name><value>Sun Oct 31 2010 21:39:50 GMT+0100</value><type>Date</type></property>
+        <property><name>sling:resourceTyp</name><value>foundation/components/image</value><type>String</type></property>
+        <node>
+          <name>file</name>
+          <primaryNodeType>nt:file</primaryNodeType>
+          <property><name>jcr:createdB</name><value>admin</value><type>String</type></property>
+          <property><name>jcr:created</name><value>Thu Aug 07 2014 16:32:59 GMT+0200</value><type>Date</type></property>
+          <node>
+            <name>jcr:content</name>
+            <primaryNodeType>nt:resource</primaryNodeType>
+            <property><name>jcr:lastModifiedBy</name><value>anonymous</value><type>String</type></property>
+            <property><name>jcr:mimeType</name><value>image/jpeg</value><type>String</type></property>
+            <property><name>jcr:lastModified</name><value>Thu Aug 07 2014 16:32:59 GMT+0200</value><type>Date</type></property>
+            <property><name>jcr:uuid</name><value>eda76d00-b2cd-4b59-878f-c33f71ceaddc</value><type>String</type></property>
+          </node>
+        </node>
+      </node>
+      <node>
+        <name>title_1</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:title</name><value>Strategic Consulting</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Mon Aug 23 2010 22:12:08 GMT+0200</value><type>Date</type></property>
+        <property><name>jcr:lastModified</name><value>Wed Oct 27 2010 21:33:24 GMT+0200</value><type>Date</type></property>
+        <property><name>sling:resourceType</name><value>sample/components/title</value><type>String</type></property>
+      </node>
+      <node>
+        <name>text_1</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Sun Oct 31 2010 21:48:04 GMT+0100</value><type>Date</type></property>
+        <property><name>text</name><value><![CDATA[<p><span class="Apple-style-span" style="font-size: 12px;">In&nbsp;today's competitive market, organizations can face several key geometric challenges:</span></p>
+<ul>
+<li><span class="Apple-style-span" style="font-size: 12px;">Polyhedral Sectioning</span></li>
+<li><span class="Apple-style-span" style="font-size: 12px;">Triangulation&nbsp;</span></li>
+<li><span class="Apple-style-span" style="font-size: 12px;">Trigonometric Calculation</span></li>
+<li><span class="Apple-style-span" style="font-size: 12px;">Ruler and Compass Construction</span></li>
+</ul>
+<p><span class="Apple-style-span" style="font-size: 12px;"><br>
+Sample is ready to help your organization deal effectively with all these challenges through our award winning geometric consulting services.</span></p>
+<p style="font-family: tahoma, arial, helvetica, sans-serif; font-size: 12px;\"><\/p>
+]]></value><type>String</type></property>
+        <property><name>jcr:lastModified</name><value>Sun Oct 31 2010 21:49:06 GMT+0100</value><type>Date</type></property>
+        <property><name>sling:resourceType</name><value>foundation/components/text</value><type>String</type></property>
+        <property><name>textIsRich</name><value>true</value><type>String</type></property>
+      </node>
+      <node>
+        <name>col_break12825937554040</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>controlType</name><value>break</value><type>String</type></property>
+        <property><name>sling:resourceType</name><value>foundation/components/parsys/colctrl</value><type>String</type></property>
+      </node>
+      <node>
+        <name>image_0</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>fileReference</name><value>/content/dam/sample/offices/clean_room.jpg</value><type>String</type></property>
+        <property><name>height</name><value>226</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Mon Aug 23 2010 22:04:46 GMT+0200</value><type>Date</type></property>
+        <property><name>jcr:lastModified</name><value>Fri Nov 05 2010 10:38:15 GMT+0100</value><type>Date</type></property>
+        <property><name>sling:resourceType</name><value>foundation/components/image</value><type>String</type></property>
+        <property><name>imageRotate</name><value>0</value><type>String</type></property>
+        <node>
+          <name>file</name>
+          <primaryNodeType>nt:file</primaryNodeType>
+          <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+          <property><name>jcr:created</name><value>Thu Aug 07 2014 16:32:59 GMT+0200</value><type>Date</type></property>
+          <node>
+            <name>jcr:content</name>
+            <primaryNodeType>nt:resource</primaryNodeType>
+            <property><name>jcr:lastModifiedBy</name><value>anonymous</value><type>String</type></property>
+            <property><name>jcr:mimeType</name><value>image/jpeg</value><type>String</type></property>
+            <property><name>jcr:lastModified</name><value>Thu Aug 07 2014 16:32:59 GMT+0200</value><type>String</type></property>
+            <property><name>jcr:uuid</name><value>6139077f-191f-4337-aaef-55456ebe6784</value><type>String</type></property>
+          </node>
+        </node>
+      </node>
+      <node>
+        <name>title_2</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:title</name><value>Shape Technology</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Mon Aug 23 2010 22:12:13 GMT+0200</value><type>Date</type></property>
+        <property><name>jcr:lastModified</name><value>Tue Oct 26 2010 21:16:29 GMT+0200</value><type>Date</type></property>
+        <property><name>sling:resourceType</name><value>sample/components/title</value><type>String</type></property>
+      </node>
+      <node>
+        <name>text_0</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Mon Aug 23 2010 22:16:30 GMT+0200</value><type>Date</type></property>
+        <property><name>text</name><value><![CDATA[<p>The Sample investment in R&amp;D has done more than solidify our industry leadership role, we have now outpaced our competitors to such an extent that we are in an altogether new space.</p>
+<p>This is why our high quality polygons and polyhedra provide the only turnkey solutions across the whole range of euclidean geometry. And our mathematicians are working on the next generation of fractal curves to bring you shapes that are unthinkable today.</p>
+<p></p>
+<p></p>
+]]></value><type>String</type></property>
+        <property><name>jcr:lastModified</name><value>Mon Nov 08 2010 20:39:00 GMT+0100</value><type>Date</type></property>
+        <property><name>sling:resourceType</name><value>foundation/components/text</value><type>String</type></property>
+        <property><name>textIsRich</name><value>true</value><type>String</type></property>
+      </node>
+      <node>
+        <name>col_end12825937444810</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>controlType</name><value>end</value><type>String</type></property>
+        <property><name>sling:resourceType</name><value>foundation/components/parsys/colctrl</value><type>String</type></property>
+      </node>
+    </node>
+    <node>
+      <name>header</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>jcr:title</name><value>trust our experience
+to manage your business</value><type>String</type></property>
+      <property><name>imageReference</name><value>/content/dam/sample/header.png</value><type>String</type></property>
+      <property><name>text</name><value>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more</value><type>String</type></property>
+      <property><name>sling:resourceType</name><value>sample/components/header</value><type>String</type></property>
+    </node>
+    <node>
+      <name>newslist</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>headline</name><value>trust our experience
+to manage your business</value><type>String</type></property>
+      <property><name>text</name><value>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more</value><type>String</type></property>
+      <property><name>sling:resourceType</name><value>sample/components/listchildren</value><type>String</type></property>
+      <property><name>listroot</name><value>/content/sample/en/about/news</value><type>String</type></property>
+    </node>
+    <node>
+      <name>lead</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>jcr:title</name><value>World Leader in Applied Geometry </value><type>String</type></property>
+      <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+      <property><name>text</name><value>Lead Text</value><type>String</type></property>
+      <property><name>title</name><value>Lead Title</value><type>String</type></property>
+      <property><name>jcr:description</name><value>ample has been selling and servicing shapes for over 2000 years. From our beginnings as a small vendor of squares and rectangles we have grown our business into a leading global provider of platonic solids and fractals. Join us as we lead geometry into the future.</value><type>String</type></property>
+      <property><name>jcr:lastModified</name><value>Wed Jan 19 2011 14:35:29 GMT+0100</value><type>Date</type></property>
+      <property><name>sling:resourceType</name><value>sample/components/lead</value><type>String</type></property>
+      <node>
+        <name>app:annotations</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+      </node>
+    </node>
+    <node>
+      <name>image</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+      <property><name>jcr:lastModified</name><value>Wed Oct 27 2010 21:30:59 GMT+0200</value><type>Date</type></property>
+      <property><name>imageRotate</name><value>0</value><type>String</type></property>
+    </node>
+    <node>
+      <name>carousel</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>playSpeed</name><value>6000</value><type>String</type></property>
+      <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+      <property><name>pages</name><values><value>/content/sample/en/events/techsummit</value><value>/content/sample/en/events/userconf</value><value>/content/sample/en/events/shapecon</value><value>/content/sample/en/events/dsc</value></values><type>String</type></property>
+      <property><name>jcr:lastModified</name><value>Tue Oct 05 2010 14:14:27 GMT+0200</value><type>Date</type></property>
+      <property><name>transTime</name><value>1000</value><type>String</type></property>
+      <property><name>sling:resourceType</name><value>foundation/components/carousel</value><type>String</type></property>
+      <property><name>listFrom</name><value>static</value><type>String</type></property>
+    </node>
+    <node>
+      <name>rightpar</name>
+      <primaryNodeType>nt:unstructured</primaryNodeType>
+      <property><name>sling:resourceType</name><value>foundation/components/parsys</value><type>String</type></property>
+      <node>
+        <name>teaser</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Tue Jan 25 2011 11:30:09 GMT+0100</value><type>Date</type></property>
+        <property><name>campaignpath</name><value>/content/campaigns/sample</value><type>String</type></property>
+        <property><name>jcr:lastModified</name><value>Wed Feb 02 2011 08:40:30 GMT+0100</value><type>Date</type></property>
+        <property><name>sling:resourceTyp</name><value>personalization/components/teaser</value><type>String</type></property>
+      </node>
+    </node>
+  </node>
+  <node>
+    <name>toolbar</name>
+    <primaryNodeType>app:Page</primaryNodeType>
+    <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+    <property><name>jcr:created</name><value>Thu Aug 07 2014 16:33:00 GMT+0200</value><type>String</type></property>
+    <node>
+      <name>jcr:content</name>
+      <primaryNodeType>app:PageContent</primaryNodeType>
+      <property><name>subtitle</name><value>Contains the toolbar</value><type>String</type></property>
+      <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+      <property><name>jcr:title</name><value>Toolbar</value><type>String</type></property>
+      <property><name>app:template</name><value>sample/templates/contentpage</value><type>String</type></property>
+      <property><name>jcr:created</name><value>Thu Aug 07 2014 16:33:00 GMT+0200</value><type>String</type></property>
+      <property><name>app:lastModified</name><value>Wed Aug 25 2010 22:51:02 GMT+0200</value><type>Date</type></property>
+      <property><name>hideInNav</name><value>true</value><type>Date</type></property>
+      <property><name>sling:resourceType</name><value>sample/components/contentpage</value><type>String</type></property>
+      <property><name>app:lastModifiedBy</name><value>admin</value><type>String</type></property>
+      <node>
+        <name>par</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>sling:resourceType</name><value>foundation/components/parsys</value><type>String</type></property>
+      </node>
+      <node>
+        <name>rightpar</name>
+        <primaryNodeType>nt:unstructured</primaryNodeType>
+        <property><name>sling:resourceType</name><value>foundation/components/iparsys</value><type>String</type></property>
+        <node>
+          <name>iparsys_fake_par</name>
+          <primaryNodeType>nt:unstructured</primaryNodeType>
+          <property><name>sling:resourceType</name><value>foundation/components/iparsys/par</value><type>String</type></property>
+        </node>
+      </node>
+    </node>
+    <node>
+      <name>profiles</name>
+      <primaryNodeType>app:Page</primaryNodeType>
+      <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+      <property><name>jcr:created</name><value>Thu Aug 07 2014 16:33:00 GMT+0200</value><type>Date</type></property>
+      <node>
+        <name>jcr:content</name>
+        <primaryNodeType>app:PageContent</primaryNodeType>
+        <mixinNodeType>type1</mixinNodeType>
+        <mixinNodeType>type2</mixinNodeType>
+        <property><name>jcr:createdBy</name><value>admin</value><type>String</type></property>
+        <property><name>jcr:title</name><value>Profiles</value><type>String</type></property>
+        <property><name>app:template</name><value>sample/templates/contentpage</value><type>String</type></property>
+        <property><name>jcr:created</name><value>Thu Aug 07 2014 16:33:00 GMT+0200</value><type>Date</type></property>
+        <property><name>app:lastModified</name><value>Thu Nov 05 2009 20:27:13 GMT+0100</value><type>Date</type></property>
+        <property><name>hideInNav</name><value>true</value><type>Boolean</type></property>
+        <property><name>sling:resourceType</name><value>sample/components/contentpage</value><type>String</type></property>
+        <property><name>app:lastModifiedBy</name><value>admin</value><type>String</type></property>
+        <property><name>longProp</name><value>1234567890123</value><type>Long</type></property>
+        <property><name>decimalProp</name><value>1.2345</value><type>Decimal</type></property>
+        <property><name>booleanProp</name><value>true</value><type>Boolean</type></property>
+        <property><name>longPropMulti</name><values><value>1234567890123</value><value>55</value></values><type>Long</type></property>
+        <property><name>decimalPropMulti</name><values><value>1.2345</value><value>1.1</value></values><type>Decimal</type></property>
+        <property><name>booleanPropMulti</name><values><value>true</value><value>false</value></values><type>Boolean</type></property>
+        <property><name>stringPropMulti</name><values><value>aa</value><value>bb</value><value>cc</value></values><type>String</type></property>
+        <node>
+          <name>par</name>
+          <primaryNodeType>nt:unstructured</primaryNodeType>
+          <property><name>sling:resourceType</name><value>foundation/components/parsys</value><type>String</type></property>
+          <node>
+            <name>textimage</name>
+            <primaryNodeType>nt:unstructured</primaryNodeType>
+            <property><name>sling:resourceType</name><value>foundation/components/textimage</value><type>String</type></property>
+          </node>
+          <node>
+            <name>mygadgets</name>
+            <primaryNodeType>nt:unstructured</primaryNodeType>
+            <property><name>gadgets</name><value>http://customer.meteogroup.de/meteogroup/gadgets/wetter24.xml
+http://germanweatherradar.googlecode.com/svn/trunk/german-weather-radar.xml
+http://www.digitalpowered.info/gadget/ski.pictures.xml
+http://www.canbuffi.de/gadgets/clock/clock.xml</value><type>String</type></property>
+            <property><name>sling:resourceType</name><value>personalization/components/mygadgets</value><type>String</type></property>
+          </node>
+        </node>
+        <node>
+          <name>rightpar</name>
+          <primaryNodeType>nt:unstructured</primaryNodeType>
+          <property><name>sling:resourceType</name><value>foundation/components/iparsys</value><type>String</type></property>
+          <node>
+            <name>iparsys_fake_par</name>
+            <primaryNodeType>nt:unstructured</primaryNodeType>
+            <property><name>sling:resourceType</name><value>foundation/components/iparsys/par</value><type>String</type></property>
+          </node>
+        </node>
+      </node>
+    </node>
+  </node>
+</node>
diff --git a/src/test/resources/invalid-test/contentWithObjectList.json b/src/test/resources/invalid-test/contentWithObjectList.json
new file mode 100644
index 0000000..e4527a0
--- /dev/null
+++ b/src/test/resources/invalid-test/contentWithObjectList.json
@@ -0,0 +1,14 @@
+{
+  "prop1": "value1",
+  "childObject": {
+    "prop2": "value2"
+  },
+  "childObjectList": [
+    {
+      "prop1": "value1"
+    },
+    {
+      "prop2": "value2"
+    }
+  ]
+}
diff --git a/src/test/resources/invalid-test/invalid.json b/src/test/resources/invalid-test/invalid.json
new file mode 100644
index 0000000..59fc86c
--- /dev/null
+++ b/src/test/resources/invalid-test/invalid.json
@@ -0,0 +1 @@
+This is invalid json.