SLING-8570 - Extract a generic Content Parser API from org.apache.sling.jcr.contentparser with pluggable implementations

* removed the ParseException and replaced it with IOException
* minor code cleanup
* improved code coverage
diff --git a/src/main/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParser.java b/src/main/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParser.java
index d402ac6..2204a29 100644
--- a/src/main/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParser.java
+++ b/src/main/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParser.java
@@ -27,7 +27,7 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.XMLConstants;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
@@ -35,11 +35,9 @@
 import org.apache.jackrabbit.util.ISO9075;
 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.ParserOptions;
 import org.osgi.service.component.annotations.Component;
 import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
 import org.xml.sax.SAXParseException;
 import org.xml.sax.helpers.DefaultHandler;
 
@@ -54,12 +52,18 @@
     private final SAXParserFactory saxParserFactory;
 
     public JCRXMLContentParser() {
-        saxParserFactory = SAXParserFactory.newInstance();
-        saxParserFactory.setNamespaceAware(true);
+        try {
+            SAXParserFactory spf = SAXParserFactory.newInstance();
+            spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
+            spf.setNamespaceAware(true);
+            saxParserFactory = spf;
+        } catch (Exception e) {
+            throw new IllegalStateException("Unable to enable secure processing.", e);
+        }
     }
 
     @Override
-    public void parse(ContentHandler handler, InputStream is, ParserOptions parserOptions) throws IOException, ParseException {
+    public void parse(ContentHandler handler, InputStream is, ParserOptions parserOptions) throws IOException {
         try {
             XmlHandler xmlHandler = new XmlHandler(handler, parserOptions);
             SAXParser parser = saxParserFactory.newSAXParser();
@@ -67,8 +71,8 @@
             if (xmlHandler.hasError()) {
                 throw xmlHandler.getError();
             }
-        } catch (ParserConfigurationException | SAXException ex) {
-            throw new ParseException("Error parsing JCR XML content.", ex);
+        } catch (Exception ex) {
+            throw new IOException("Error parsing JCR XML content.", ex);
         }
     }
 
@@ -85,7 +89,7 @@
     /**
      * Parses XML stream to Map.
      */
-    class XmlHandler extends DefaultHandler {
+    private static class XmlHandler extends DefaultHandler {
         private final ContentHandler contentHandler;
         private final ParserOptions parserOptions;
         private final Deque<String> paths = new ArrayDeque<>();
@@ -140,10 +144,8 @@
                 }
             }
             String defaultPrimaryType = parserOptions.getDefaultPrimaryType();
-            if (defaultPrimaryType != null) {
-                if (!properties.containsKey(JcrConstants.JCR_PRIMARYTYPE)) {
-                    properties.put(JcrConstants.JCR_PRIMARYTYPE, defaultPrimaryType);
-                }
+            if (defaultPrimaryType != null && !properties.containsKey(JcrConstants.JCR_PRIMARYTYPE)) {
+                properties.put(JcrConstants.JCR_PRIMARYTYPE, defaultPrimaryType);
             }
             contentHandler.resource(path, properties);
         }
@@ -170,8 +172,8 @@
             if (ignoredPaths.contains(path)) {
                 return true;
             }
-            if (path.contains("/")) {
-                String parentPath = path.substring(0, path.lastIndexOf("/"));
+            if (path.indexOf('/') > -1) {
+                String parentPath = path.substring(0, path.lastIndexOf('/'));
                 return isIgnoredPath(parentPath);
             } else {
                 return isIgnoredPath(path);
diff --git a/src/test/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParserTest.java b/src/test/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParserTest.java
index 349a991..19707f5 100644
--- a/src/test/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParserTest.java
+++ b/src/test/java/org/apache/sling/contentparser/xml/jcr/internal/JCRXMLContentParserTest.java
@@ -19,6 +19,7 @@
 package org.apache.sling.contentparser.xml.jcr.internal;
 
 import java.io.File;
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -29,7 +30,6 @@
 
 import org.apache.jackrabbit.util.ISO9075;
 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;
@@ -62,7 +62,7 @@
         assertEquals("app:PageContent", child.getProperties().get("jcr:primaryType"));
     }
 
-    @Test(expected= ParseException.class)
+    @Test(expected= IOException.class)
     public void testParseInvalidJcrXml() throws Exception {
         file = new File("src/test/resources/invalid-test/invalid.jcr.xml");
         parse(underTest, file);