SLING-7011 fsresource: Detect FileVault <any>.xml JCR XML files

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1801932 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index acc856f..62446d5 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -314,6 +314,7 @@
         List<String> contentFileSuffixes = new ArrayList<>();
         if (fsMode == FsMode.FILEVAULT_XML) {
             contentFileSuffixes.add("/" + DOT_CONTENT_XML);
+            contentFileSuffixes.add(ContentFileTypes.XML_SUFFIX);
             if (StringUtils.isNotBlank(config.provider_filevault_filterxml_path())) {
                 filterXmlFile = new File(config.provider_filevault_filterxml_path());
             }
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 184d184..853fe82 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
@@ -26,6 +26,7 @@
 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;
+import org.apache.sling.jcr.contentparser.ContentType;
 
 /**
  * Reference to a file that contains a content fragment (e.g. JSON, JCR XML).
@@ -36,6 +37,7 @@
     private final String path;
     private final String subPath;
     private final ContentFileCache contentFileCache;
+    private final ContentType contentType;
     private boolean contentInitialized;
     private ContentElement content;
     private ValueMap valueMap;
@@ -47,10 +49,22 @@
      * @param contentFileCache Content file cache
      */
     public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache) {
+        this(file, path, subPath, contentFileCache, null);
+    }
+
+    /**
+     * @param file File with content fragment
+     * @param path Root path of the content file
+     * @param subPath Relative path addressing content fragment inside file
+     * @param contentFileCache Content file cache
+     * @param contentType Content type
+     */
+    public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache, ContentType contentType) {
         this.file = file;
         this.path = path;
         this.subPath = subPath;
         this.contentFileCache = contentFileCache;
+        this.contentType = contentType;
     }
 
     /**
@@ -80,7 +94,7 @@
      */
     public ContentElement getContent() {
         if (!contentInitialized) {
-            ContentElement rootContent = contentFileCache.get(path, file);
+            ContentElement rootContent = contentFileCache.get(path, file, contentType);
             if (subPath == null) {
                 content = rootContent;
             }
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 14f6146..77c5554 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
@@ -19,6 +19,7 @@
 package org.apache.sling.fsprovider.internal.mapper;
 
 import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
+import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.XML_SUFFIX;
 
 import java.io.File;
 import java.io.IOException;
@@ -40,13 +41,15 @@
 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.apache.sling.jcr.contentparser.ContentType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public final class FileVaultResourceMapper implements FsResourceMapper {
     
     private static final String DOT_CONTENT_XML_SUFFIX = "/" + DOT_CONTENT_XML;
-    private static final String DOT_DIR_SUFFIX = "/.dir";
+    private static final String DOT_DIR = ".dir";
+    private static final String DOT_DIR_SUFFIX = "/" + DOT_DIR;
 
     private final File providerFile;
     private final File filterXmlFile;
@@ -173,6 +176,9 @@
         }
         File file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path));
         if (file.exists()) {
+            if (StringUtils.endsWith(path, XML_SUFFIX) && !hasDotDirFile(file)) {
+                return null;
+            }
             return file;
         }
         return null;
@@ -181,7 +187,15 @@
     private ContentFile getContentFile(String path, String subPath) {
         File file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path) + DOT_CONTENT_XML_SUFFIX);
         if (file.exists()) {
-            ContentFile contentFile = new ContentFile(file, path, subPath, contentFileCache);
+            ContentFile contentFile = new ContentFile(file, path, subPath, contentFileCache, ContentType.JCR_XML);
+            if (contentFile.hasContent()) {
+                return contentFile;
+            }
+        }
+        
+        file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path) + XML_SUFFIX);
+        if (file.exists() && !hasDotDirFile(file)) {
+            ContentFile contentFile = new ContentFile(file, path, subPath, contentFileCache, ContentType.JCR_XML);
             if (contentFile.hasContent()) {
                 return contentFile;
             }
@@ -196,5 +210,13 @@
                 + (subPath != null ? "/" + subPath : "");
         return getContentFile(parentPath, nextSubPath);
     }
+    
+    private boolean hasDotDirFile(File file) {
+        File dotDir = new File(file.getPath() + DOT_DIR);
+        if (dotDir.exists() && dotDir.isDirectory()) {
+            return true;
+        }
+        return false;
+    }
 
 }
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 4222e98..e18a0ff 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
@@ -23,6 +23,7 @@
 import java.util.Map;
 
 import org.apache.commons.collections.map.LRUMap;
+import org.apache.sling.jcr.contentparser.ContentType;
 
 /**
  * Cache for parsed content from content files (e.g. JSON, JCR XML).
@@ -52,12 +53,28 @@
      * @return Content or null
      */
     public ContentElement get(String path, File file) {
+        return get(path, file, null);
+    }
+    
+    /**
+     * Get content.
+     * @param path Path (used as cache key).
+     * @param file File
+     * @param contentType Content type - if null type is auto-detected
+     * @return Content or null
+     */
+    public ContentElement get(String path, File file, ContentType contentType) {
         ContentElement content = null;
         if (contentCache != null) {
             content = contentCache.get(path);
         }
         if (content == null) {
-            content = ContentFileParserUtil.parse(file);
+            if (contentType != null) {
+                content = ContentFileParserUtil.parse(file, contentType);
+            }
+            else {
+                content = ContentFileParserUtil.parse(file);
+            }
             if (content == null) {
                 content = NULL_ELEMENT;
             }
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 a105dad..e0d0e0c 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
@@ -75,15 +75,38 @@
         if (!file.exists()) {
             return null;
         }
+        if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
+            return parse(file, ContentType.JSON);
+        }
+        else if (StringUtils.equals(file.getName(), DOT_CONTENT_XML) || StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
+            return parse(file, ContentType.JCR_XML);
+        }
+        else if (StringUtils.endsWith(file.getName(), XML_SUFFIX) && !StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
+            return parse(file, ContentType.XML);
+        }
+        return null;
+    }
+    
+    /**
+     * Parse content from file.
+     * @param file File. Type is detected automatically.
+     * @param contentType Content type
+     * @return Content or null if content could not be parsed.
+     */
+    public static ContentElement parse(File file, ContentType contentType) {
+        if (!file.exists()) {
+            return null;
+        }
         try {
-            if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
+            switch (contentType) {
+            case JSON:
                 return parse(JSON_PARSER, file);
-            }
-            else if (StringUtils.equals(file.getName(), DOT_CONTENT_XML) || StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
-                return parse(JCR_XML_PARSER, file);
-            }
-            else if (StringUtils.endsWith(file.getName(), XML_SUFFIX) && !StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
+            case XML:
                 return parse(XML_PARSER, file);
+            case JCR_XML:
+                return parse(JCR_XML_PARSER, file);
+               default:
+                    throw new IllegalArgumentException("Unexpected content type: " + contentType);
             }
         }
         catch (Throwable ex) {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
index b2f1514..35b51d1 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
@@ -107,7 +107,7 @@
     public void testListChildren() {
         Resource en = sampleContent.getChild("en");
         List<Resource> children = ImmutableList.copyOf(en.listChildren());
-        assertEquals(2, children.size());
+        assertEquals(3, children.size());
         
         Resource child1 = children.get(0);
         assertEquals("jcr:content", child1.getName());
@@ -117,7 +117,10 @@
         assertEquals("tools", child2.getName());
         assertEquals("app:Page", child2.getResourceType());
         
-        // child3 (conference) is hidden because of filter
+        Resource child3 = children.get(2);
+        assertEquals("extra", child3.getName());
+        
+        // another child (conference) is hidden because of filter
     }
 
     @Test
@@ -143,7 +146,22 @@
 
         // list children with mixed content
         Resource enResource = sampleContent.getChild("en");
-        assertThat(enResource, ResourceMatchers.containsChildren("jcr:content", "tools", "conference"));
+        assertThat(enResource, ResourceMatchers.containsChildren("jcr:content", "tools", "extra", "conference"));
     }
 
+    @Test
+    public void testExtraContent() throws RepositoryException {
+        Resource extraContent = sampleContent.getChild("en/extra/extracontent");
+        assertNotNull(extraContent);
+        assertEquals("apps/app1/components/comp1", extraContent.getResourceType());
+        
+        Resource layout = extraContent.getChild("layout");
+        assertNotNull(layout);
+        assertEquals("apps/app1/components/comp2", layout.getResourceType());
+
+        Resource binaryFile = sampleContent.getChild("en/extra/binaryfile.xml");
+        assertNotNull(binaryFile);
+        assertEquals("nt:file", binaryFile.getResourceType());
+    }
+    
 }
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml
index 83b8626..c394cf9 100644
--- a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml
@@ -188,4 +188,8 @@
   </jcr:content>
   <tools />
   <conference />
+  <extra jcr:primaryType="nt:unstructured">
+    <extracontent/>
+    <binaryfile.xml/>
+  </extra>
 </jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/binaryfile.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/binaryfile.xml
new file mode 100644
index 0000000..63b5974
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/binaryfile.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="nt:unstructured"
+    sling:resourceType="apps/app1/components/comp1">
+    <layout
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="apps/app1/components/comp2"/>
+    <field jcr:primaryType="nt:unstructured"/>
+    <action jcr:primaryType="nt:unstructured"/>
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/binaryfile.xml.dir/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/binaryfile.xml.dir/.content.xml
new file mode 100644
index 0000000..c0ce9be
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/binaryfile.xml.dir/.content.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+    jcr:primaryType="nt:file">
+  <jcr:content
+      jcr:mimeType="application/xml"
+      jcr:primaryType="nt:resource" />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/extracontent.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/extracontent.xml
new file mode 100644
index 0000000..63b5974
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/extra/extracontent.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
+    jcr:primaryType="nt:unstructured"
+    sling:resourceType="apps/app1/components/comp1">
+    <layout
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="apps/app1/components/comp2"/>
+    <field jcr:primaryType="nt:unstructured"/>
+    <action jcr:primaryType="nt:unstructured"/>
+</jcr:root>