You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:37:34 UTC

[sling-org-apache-sling-fsresource] 19/23: SLING-6440 switch to latest contentparser API

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

rombert pushed a commit to annotated tag org.apache.sling.fsresource-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit aa6fca15821be8bb1c73311302e96dcaff24ea9e
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 17 21:56:21 2017 +0000

    SLING-6440 switch to latest contentparser API
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/branches/fsresource-1.x@1787508 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/fsprovider/internal/FileMonitor.java     | 27 ++++-----
 .../fsprovider/internal/mapper/ContentFile.java    | 70 +++++-----------------
 .../internal/mapper/ContentFileResource.java       |  2 +-
 .../internal/mapper/ContentFileResourceMapper.java |  7 ++-
 .../internal/mapper/FileVaultResourceMapper.java   |  5 +-
 .../internal/mapper/jcr/FsNodeIterator.java        | 18 ++----
 .../internal/mapper/valuemap/ValueMapUtil.java     |  8 +--
 .../fsprovider/internal/parser/ContentElement.java | 52 ++++++++++++++++
 .../internal/parser/ContentElementHandler.java     | 69 +++++++++++++++++++++
 .../internal/parser/ContentElementImpl.java        | 68 +++++++++++++++++++++
 .../internal/parser/ContentFileCache.java          | 12 ++--
 .../internal/parser/ContentFileParserUtil.java     | 13 ++--
 .../internal/mapper/ContentFileTest.java           | 25 +++-----
 .../internal/mapper/valuemap/ValueMapUtilTest.java |  4 --
 .../internal/parser/ContentFileCacheTest.java      |  5 +-
 .../internal/parser/ContentFileParserUtilTest.java | 19 +++---
 16 files changed, 264 insertions(+), 140 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index f4db089..876c167 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -38,6 +38,8 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
 import org.apache.sling.fsprovider.internal.mapper.FileResource;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
+import org.apache.sling.fsprovider.internal.parser.ContentElementImpl;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.osgi.service.event.EventAdmin;
 import org.slf4j.Logger;
@@ -261,47 +263,44 @@ public final class FileMonitor extends TimerTask {
         }
     }
     
-    @SuppressWarnings("unchecked")
     private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final String changeType) {
         List<ResourceChange> changes = new ArrayList<>();
         if (monitorable.status instanceof ContentFileStatus) {
             ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile;
             if (StringUtils.equals(changeType, TOPIC_RESOURCE_CHANGED)) {
-                Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
+                ContentElement content = contentFile.getContent();
                 // we cannot easily report the diff of resource changes between two content files
                 // so we simulate a removal of the toplevel node and then add all nodes contained in the current content file again.
                 changes.add(buildContentResourceChange(TOPIC_RESOURCE_REMOVED, content, transformPath(monitorable.path)));
                 addContentResourceChanges(changes, TOPIC_RESOURCE_ADDED, content, transformPath(monitorable.path));
             }
             else {
-                addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), transformPath(monitorable.path));
+                addContentResourceChanges(changes, changeType, contentFile.getContent(), transformPath(monitorable.path));
             }
         }
         else {
-            Map<String,Object> content = new HashMap<>();
-            content.put("sling:resourceType", monitorable.status instanceof FileStatus ?
+            Map<String,Object> props = new HashMap<>();
+            props.put("sling:resourceType", monitorable.status instanceof FileStatus ?
                     FileResource.RESOURCE_TYPE_FILE : FileResource.RESOURCE_TYPE_FOLDER);
+            ContentElement content = new ContentElementImpl(null, props);
             changes.add(buildContentResourceChange(changeType, content, transformPath(monitorable.path)));
         }
         return changes;
     }
-    @SuppressWarnings("unchecked")
     private void addContentResourceChanges(final List<ResourceChange> changes, final String changeType,
-            final Map<String,Object> content, final String path) {
+            final ContentElement content, final String path) {
         changes.add(buildContentResourceChange(changeType, content, path));
         if (content != null) {
-            for (Map.Entry<String,Object> entry : content.entrySet()) {
-                if (entry.getValue() instanceof Map) {
-                    String childPath = path + "/" + entry.getKey();
-                    addContentResourceChanges(changes, changeType, (Map<String,Object>)entry.getValue(), childPath);
-                }
+            for (Map.Entry<String,ContentElement> entry : content.getChildren().entrySet()) {
+                String childPath = path + "/" + entry.getKey();
+                addContentResourceChanges(changes, changeType, entry.getValue(), childPath);
             }
         }
     }
-    private ResourceChange buildContentResourceChange(final String changeType, final Map<String,Object> content, final String path) {
+    private ResourceChange buildContentResourceChange(final String changeType, final ContentElement content, final String path) {
         ResourceChange change = new ResourceChange();
         change.path = path;
-        change.resourceType = content != null ? (String)content.get("sling:resourceType") : null;
+        change.resourceType = content != null ? (String)content.getProperties().get("sling:resourceType") : null;
         change.topic = changeType;
         return change;
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
index 00c238a..dd31346 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -22,10 +22,9 @@ import java.io.File;
 import java.util.Iterator;
 import java.util.Map;
 
-import org.apache.commons.collections.IteratorUtils;
-import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapUtil;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
 /**
@@ -38,7 +37,7 @@ public final class ContentFile {
     private final String subPath;
     private final ContentFileCache contentFileCache;
     private boolean contentInitialized;
-    private Object content;
+    private ContentElement content;
     private ValueMap valueMap;
     
     /**
@@ -79,10 +78,15 @@ public final class ContentFile {
      * Content object referenced by sub path.
      * @return Map if resource, property value if property.
      */
-    public Object getContent() {
+    public ContentElement getContent() {
         if (!contentInitialized) {
-            Map<String,Object> rootContent = contentFileCache.get(path, file);
-            content = getDeepContent(rootContent, subPath);
+            ContentElement rootContent = contentFileCache.get(path, file);
+            if (subPath == null) {
+                content = rootContent;
+            }
+            else {
+                content = rootContent.getChild(subPath);
+            }
             contentInitialized = true;
         }
         return content;
@@ -96,21 +100,13 @@ public final class ContentFile {
     }
     
     /**
-     * @return true if content references resource map.
-     */
-    public boolean isResource() {
-        return (getContent() instanceof Map);
-    }
-    
-    /**
      * @return ValueMap for resource. Never null.
      */
-    @SuppressWarnings("unchecked")
     public ValueMap getValueMap() {
         if (valueMap == null) {
-            Object currentContent = getContent();
-            if (currentContent instanceof Map) {
-                valueMap = ValueMapUtil.toValueMap((Map<String,Object>)currentContent);
+            ContentElement currentContent = getContent();
+            if (currentContent != null) {
+                valueMap = ValueMapUtil.toValueMap(currentContent.getProperties());
             }
             else {
                 valueMap = ValueMap.EMPTY;
@@ -122,18 +118,8 @@ public final class ContentFile {
     /**
      * @return Child maps.
      */
-    @SuppressWarnings("unchecked")
-    public Iterator<Map.Entry<String,Map<String,Object>>> getChildren() {
-        if (!isResource()) {
-            return IteratorUtils.emptyIterator();
-        }
-        return IteratorUtils.filteredIterator(((Map)getContent()).entrySet().iterator(), new Predicate() {
-            @Override
-            public boolean evaluate(Object object) {
-                Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object;
-                return entry.getValue() instanceof Map;
-            }
-        });
+    public Iterator<Map.Entry<String,ContentElement>> getChildren() {
+        return getContent().getChildren().entrySet().iterator();
     }
     
     /**
@@ -161,30 +147,4 @@ public final class ContentFile {
         return new ContentFile(file, path, absoluteSubPath, contentFileCache);
     }
         
-    @SuppressWarnings("unchecked")
-    private static Object getDeepContent(Object object, String subPath) {
-        if (object == null) {
-            return null;
-        }
-        if (subPath == null) {
-            return object;
-        }
-        if (!(object instanceof Map)) {
-            return null;
-        }
-        String name;
-        String remainingSubPath;
-        int slashIndex = subPath.indexOf('/');
-        if (slashIndex >= 0) {
-            name = subPath.substring(0, slashIndex);
-            remainingSubPath = subPath.substring(slashIndex + 1);
-        }
-        else {
-            name = subPath;
-            remainingSubPath = null;
-        }
-        Object subObject = ((Map<String,Object>)object).get(name);
-        return getDeepContent(subObject, remainingSubPath);
-    }
-    
 }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
index 622deba..f65422d 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
@@ -107,7 +107,7 @@ public final class ContentFileResource extends AbstractResource {
         else if (type == ValueMap.class) {
             return (AdapterType)contentFile.getValueMap();
         }
-        else if (type == Node.class && contentFile.isResource()) {
+        else if (type == Node.class) {
             // support a subset of JCR API for content file resources
             return (AdapterType)new FsNode(contentFile, getResourceResolver());
         }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
index 09b6ffe..b44926a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
@@ -32,6 +32,7 @@ import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.fsprovider.internal.ContentFileExtensions;
 import org.apache.sling.fsprovider.internal.FsResourceMapper;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
 public final class ContentFileResourceMapper implements FsResourceMapper {
@@ -105,10 +106,10 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
 
         // get child resources from content fragments in content file
         List<ContentFile> children = new ArrayList<>();
-        if (parentContentFile.hasContent() && parentContentFile.isResource()) {
-            Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren();
+        if (parentContentFile.hasContent()) {
+            Iterator<Map.Entry<String,ContentElement>> childMaps = parentContentFile.getChildren();
             while (childMaps.hasNext()) {
-                Map.Entry<String,Map<String,Object>> entry = childMaps.next();
+                Map.Entry<String,ContentElement> entry = childMaps.next();
                 children.add(parentContentFile.navigateToRelative(entry.getKey()));
             }
         }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
index 6585d1e..14f6146 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
@@ -38,6 +38,7 @@ import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.fsprovider.internal.FsResourceMapper;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -94,9 +95,9 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
         // get children from content resource of parent
         ContentFile parentContentFile = getContentFile(parentPath, null);
         if (parentContentFile != null) {
-            Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren();
+            Iterator<Map.Entry<String,ContentElement>> childMaps = parentContentFile.getChildren();
             while (childMaps.hasNext()) {
-                Map.Entry<String,Map<String,Object>> entry = childMaps.next();
+                Map.Entry<String,ContentElement> entry = childMaps.next();
                 String childPath = parentPath + "/" + entry.getKey();
                 if (pathMatches(childPath)) {
                     childPaths.add(childPath);
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
index 09db7d4..7565994 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
@@ -24,10 +24,9 @@ import java.util.Map;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 
-import org.apache.commons.collections.IteratorUtils;
-import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 
 /**
  * Simplified implementation of read-only content access via the JCR API.
@@ -36,20 +35,13 @@ class FsNodeIterator implements NodeIterator {
     
     private final ContentFile contentFile;
     private final ResourceResolver resolver;
-    private final Iterator<Map.Entry<String,Map<String,Object>>> children;
+    private final Iterator<Map.Entry<String,ContentElement>> children;
 
-    @SuppressWarnings("unchecked")
     public FsNodeIterator(ContentFile contentFile, ResourceResolver resolver) {
         this.contentFile = contentFile;
         this.resolver = resolver;
-        Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
-        this.children = IteratorUtils.filteredIterator(content.entrySet().iterator(), new Predicate() {
-            @Override
-            public boolean evaluate(Object object) {
-                Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object;
-                return (entry.getValue() instanceof Map);
-            }
-        });
+        ContentElement content = contentFile.getContent();
+        this.children = content.getChildren().entrySet().iterator();
     }
 
     public boolean hasNext() {
@@ -62,7 +54,7 @@ class FsNodeIterator implements NodeIterator {
 
     @Override
     public Node nextNode() {
-        Map.Entry<String,Map<String,Object>> nextEntry = children.next();
+        Map.Entry<String,ContentElement> nextEntry = children.next();
         return new FsNode(contentFile.navigateToRelative(nextEntry.getKey()), resolver);
     }
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
index b0a079c..ce69941 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
@@ -38,12 +38,8 @@ public final class ValueMapUtil {
     public static ValueMap toValueMap(Map<String,Object> content) {
         Map<String,Object> props = new HashMap<>();
         
-        for (Map.Entry<String, Object> entry : ((Map<String,Object>)content).entrySet()) {
-            if (entry.getValue() instanceof Map) {
-                // skip child resources
-                continue;
-            }
-            else if (entry.getValue() instanceof Collection) {
+        for (Map.Entry<String, Object> entry : content.entrySet()) {
+            if (entry.getValue() instanceof Collection) {
                 // convert lists to arrays
                 props.put(entry.getKey(), ((Collection)entry.getValue()).toArray());
             }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java
new file mode 100644
index 0000000..85ead62
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.fsprovider.internal.parser;
+
+import java.util.Map;
+
+/**
+ * Represents a resource or node in the content hierarchy.
+ */
+public interface ContentElement {
+
+    /**
+     * @return Resource name. The root resource has no name (null).
+     */
+    String getName();
+    
+    /**
+     * Properties of this resource.
+     * @return Properties (keys, values)
+     */
+    Map<String, Object> getProperties();
+    
+    /**
+     * Get children of current resource. The Map preserves the ordering of children.
+     * @return Children (child names, child objects)
+     */
+    Map<String, ContentElement> getChildren();
+    
+    /**
+     * Get child or descendant
+     * @param path Relative path to address child or one of it's descendants (use "/" as hierarchy separator).
+     * @return Child or null if no child found with this path
+     */
+    ContentElement getChild(String path);
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java
new file mode 100644
index 0000000..e128943
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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.fsprovider.internal.parser;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.jcr.contentparser.ContentHandler;
+
+/**
+ * {@link ContentHandler} implementation that produces a tree of {@link ContentElement} items.
+ */
+final class ContentElementHandler implements ContentHandler {
+    
+    private ContentElement root;
+    private Pattern PATH_PATTERN = Pattern.compile("^((/[^/]+)*)(/([^/]+))$"); 
+
+    @Override
+    public void resource(String path, Map<String, Object> properties) {
+        if (StringUtils.equals(path, "/")) {
+            root = new ContentElementImpl(null, properties);
+        }
+        else {
+            if (root == null) {
+                throw new RuntimeException("Root resource not set.");
+            }
+            Matcher matcher = PATH_PATTERN.matcher(path);
+            if (!matcher.matches()) {
+                throw new RuntimeException("Unexpected path:" + path);
+            }
+            String relativeParentPath = StringUtils.stripStart(matcher.group(1), "/");
+            String name = matcher.group(4);
+            ContentElement parent;
+            if (StringUtils.isEmpty(relativeParentPath)) {
+                parent = root;
+            }
+            else {
+                parent = root.getChild(relativeParentPath);
+            }
+            if (parent == null) {
+                throw new RuntimeException("Parent '" + relativeParentPath + "' does not exist.");
+            }
+            parent.getChildren().put(name, new ContentElementImpl(name, properties));
+        }
+    }
+    
+    public ContentElement getRoot() {
+        return root;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java
new file mode 100644
index 0000000..5205c6e
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.fsprovider.internal.parser;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+public final class ContentElementImpl implements ContentElement {
+    
+    private final String name;
+    private final Map<String, Object> properties;
+    private final Map<String, ContentElement> children = new LinkedHashMap<>();
+    
+    public ContentElementImpl(String name, Map<String, Object> properties) {
+        this.name = name;
+        this.properties = properties;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public Map<String, ContentElement> getChildren() {
+        return children;
+    }
+
+    @Override
+    public ContentElement getChild(String path) {
+        String name = StringUtils.substringBefore(path, "/");
+        ContentElement child = children.get(name);
+        if (child == null) {
+          return null;
+        }
+        String remainingPath = StringUtils.substringAfter(path, "/");
+        if (StringUtils.isEmpty(remainingPath)) {
+          return child;
+        }
+        else {
+          return child.getChild(remainingPath);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
index 745ae60..4222e98 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
@@ -29,8 +29,8 @@ import org.apache.commons.collections.map.LRUMap;
  */
 public final class ContentFileCache {
 
-    private final Map<String,Map<String,Object>> contentCache;
-    private final Map<String,Object> NULL_MAP = Collections.emptyMap();
+    private final Map<String,ContentElement> contentCache;
+    private final ContentElement NULL_ELEMENT = new ContentElementImpl(null, Collections.<String,Object>emptyMap());
     
     /**
      * @param maxSize Cache size. 0 = caching disabled.
@@ -51,21 +51,21 @@ public final class ContentFileCache {
      * @param file File
      * @return Content or null
      */
-    public Map<String,Object> get(String path, File file) {
-        Map<String,Object> content = null;
+    public ContentElement get(String path, File file) {
+        ContentElement content = null;
         if (contentCache != null) {
             content = contentCache.get(path);
         }
         if (content == null) {
             content = ContentFileParserUtil.parse(file);
             if (content == null) {
-                content = NULL_MAP;
+                content = NULL_ELEMENT;
             }
             if (contentCache != null) {
                 contentCache.put(path, content);
             }
         }
-        if (content == NULL_MAP) {
+        if (content == NULL_ELEMENT) {
             return null;
         }
         else {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index cb7e222..dbb638a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -26,7 +26,6 @@ import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.jcr.contentparser.ContentParser;
@@ -65,7 +64,7 @@ class ContentFileParserUtil {
      * @param file File. Type is detected automatically.
      * @return Content or null if content could not be parsed.
      */
-    public static Map<String,Object> parse(File file) {
+    public static ContentElement parse(File file) {
         if (!file.exists()) {
             return null;
         }
@@ -82,12 +81,14 @@ class ContentFileParserUtil {
         }
         return null;
     }
-
-    private static Map<String,Object> parse(ContentParser contentParser, File file) throws IOException {
+    
+    private static ContentElement parse(ContentParser contentParser, File file) throws IOException {
         try (FileInputStream fis = new FileInputStream(file);
                 BufferedInputStream bis = new BufferedInputStream(fis)) {
-            return contentParser.parse(bis);
+            ContentElementHandler handler = new ContentElementHandler();
+            contentParser.parse(handler, bis);
+            return handler.getRoot();
         }
     }
-    
+
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
index 4659991..3a6a747 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
@@ -24,9 +24,9 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
-import java.util.Map;
 
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.junit.Test;
 
@@ -34,7 +34,6 @@ public class ContentFileTest {
     
     private ContentFileCache contentFileCache = new ContentFileCache(0);
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testRootContent() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
@@ -45,16 +44,15 @@ public class ContentFileTest {
         
         assertTrue(underTest.hasContent());
 
-        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
-        assertEquals("app:Page", content.get("jcr:primaryType"));
-        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+        ContentElement content = underTest.getContent();
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+        assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType"));
 
         ValueMap props = underTest.getValueMap();
         assertEquals("app:Page", props.get("jcr:primaryType"));
         assertNull(props.get("jcr:content"));
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testContentLevel1() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
@@ -65,14 +63,13 @@ public class ContentFileTest {
         
         assertTrue(underTest.hasContent());
 
-        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
-        assertEquals("app:PageContent", content.get("jcr:primaryType"));
+        ContentElement content = underTest.getContent();
+        assertEquals("app:PageContent", content.getProperties().get("jcr:primaryType"));
 
         ValueMap props = underTest.getValueMap();
         assertEquals("app:PageContent", props.get("jcr:primaryType"));
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testContentLevel5() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
@@ -83,8 +80,8 @@ public class ContentFileTest {
         
         assertTrue(underTest.hasContent());
 
-        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
-        assertEquals("nt:resource", content.get("jcr:primaryType"));
+        ContentElement content = underTest.getContent();
+        assertEquals("nt:resource", content.getProperties().get("jcr:primaryType"));
 
         ValueMap props = underTest.getValueMap();
         assertEquals("nt:resource", props.get("jcr:primaryType"));
@@ -98,11 +95,7 @@ public class ContentFileTest {
         assertEquals(file, underTest.getFile());
         assertEquals("jcr:content/jcr:title", underTest.getSubPath());
         
-        assertTrue(underTest.hasContent());
-
-        assertEquals("English", underTest.getContent());
-
-        assertTrue(underTest.getValueMap().isEmpty());
+        assertFalse(underTest.hasContent());
     }
 
     @Test
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
index 87f2bfd..841ef00 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
@@ -20,7 +20,6 @@ package org.apache.sling.fsprovider.internal.mapper.valuemap;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -29,7 +28,6 @@ import org.apache.sling.api.resource.ValueMap;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 public class ValueMapUtilTest {
 
@@ -38,7 +36,6 @@ public class ValueMapUtilTest {
         Map<String,Object> content = new HashMap<>();
         content.put("stringProp", "abc");
         content.put("intProp", 123);
-        content.put("childNode", ImmutableMap.<String,Object>of());
         content.put("stringArray", new String[] { "a", "b", "c" });
         content.put("stringList", ImmutableList.of("ab", "cd"));
         content.put("intList", ImmutableList.of(12, 34));
@@ -46,7 +43,6 @@ public class ValueMapUtilTest {
         ValueMap props = ValueMapUtil.toValueMap(content);
         assertEquals("abc", props.get("stringProp", String.class));
         assertEquals((Integer)123, props.get("intProp", 0));
-        assertNull(props.get("childNode"));
         assertArrayEquals(new String[] { "a", "b", "c" }, props.get("stringArray", String[].class));
         assertArrayEquals(new String[] { "ab", "cd" }, props.get("stringList", String[].class));
         assertArrayEquals(new Integer[] { 12, 34 }, props.get("intList", Integer[].class));
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
index 1eaf1e1..236959a 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
@@ -23,7 +23,6 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import java.io.File;
-import java.util.Map;
 
 import org.junit.experimental.theories.DataPoint;
 import org.junit.experimental.theories.Theories;
@@ -44,7 +43,7 @@ public class ContentFileCacheTest {
     public void testCache(int cacheSize) {
         ContentFileCache underTest = new ContentFileCache(cacheSize);
         
-        Map<String,Object> content1 = underTest.get("/fs-test/folder2/content", new File("src/test/resources/fs-test/folder2/content.json"));
+        ContentElement content1 = underTest.get("/fs-test/folder2/content", new File("src/test/resources/fs-test/folder2/content.json"));
         assertNotNull(content1);
         
         switch (cacheSize) {
@@ -57,7 +56,7 @@ public class ContentFileCacheTest {
             break;
         }
 
-        Map<String,Object> content2 = underTest.get("/fs-test/folder1/file1a", new File("src/test/resources/fs-test/folder1/file1a.txt"));
+        ContentElement content2 = underTest.get("/fs-test/folder1/file1a", new File("src/test/resources/fs-test/folder1/file1a.txt"));
         assertNull(content2);
 
         switch (cacheSize) {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
index 9bdaa51..b775c81 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
@@ -23,43 +23,40 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import java.io.File;
-import java.util.Map;
 
 import org.junit.Test;
 
 public class ContentFileParserUtilTest {
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testParseJson() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNotNull(content);
-        assertEquals("app:Page", content.get("jcr:primaryType"));
-        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+        assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType"));
     }
 
     @Test
     public void testParseInvalidJson() {
         File file = new File("src/test/resources/invalid-test/invalid.json");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNull(content);
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testParseJcrXml() {
         File file = new File("src/test/resources/fs-test/folder3/content.jcr.xml");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNotNull(content);
-        assertEquals("app:Page", content.get("jcr:primaryType"));
-        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+        assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType"));
     }
 
     @Test
     public void testParseInvalidJcrXml() {
         File file = new File("src/test/resources/invalid-test/invalid.jcr.xml");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNull(content);
     }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.