This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.jcr.contentparser-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentparser.git
commit c8c0f239df205626b411aad50cd1371ceb5c169c Author: Stefan Seifert <[email protected]> AuthorDate: Fri Mar 17 20:59:21 2017 +0000 SLING-6592 switch to Stream API for content parsing, remove content representation API git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/contentparser@1787499 13f79535-47bb-0310-9956-ffa450edef68 --- .../{ContentParser.java => ContentHandler.java} | 21 +++---- .../sling/jcr/contentparser/ContentParser.java | 9 ++- .../jcr/contentparser/ContentParserFactory.java | 2 +- .../sling/jcr/contentparser/ContentType.java | 2 +- .../sling/jcr/contentparser/ParserOptions.java | 4 +- .../contentparser/impl/JcrXmlContentParser.java | 67 ++++++++++++++------- .../jcr/contentparser/impl/JsonContentParser.java | 44 ++++++++++---- .../sling/jcr/contentparser/impl/ParserHelper.java | 13 +++- .../sling/jcr/contentparser/package-info.java | 2 +- .../impl/JcrXmlContentParserTest.java | 55 ++++++++++------- .../contentparser/impl/JsonContentParserTest.java | 61 ++++++++++++------- .../sling/jcr/contentparser/impl/TestUtils.java | 27 ++------- .../impl/mapsupport/ContentElement.java} | 44 +++++++------- .../impl/mapsupport/ContentElementHandler.java | 69 ++++++++++++++++++++++ .../impl/mapsupport/ContentElementImpl.java | 68 +++++++++++++++++++++ 15 files changed, 348 insertions(+), 140 deletions(-) diff --git a/src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java b/src/main/java/org/apache/sling/jcr/contentparser/ContentHandler.java similarity index 62% copy from src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java copy to src/main/java/org/apache/sling/jcr/contentparser/ContentHandler.java index 1d9595a..7582338 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/ContentHandler.java @@ -18,23 +18,20 @@ */ package org.apache.sling.jcr.contentparser; -import java.io.IOException; -import java.io.InputStream; import java.util.Map; /** - * Parses repository content from a file. - * Implementations have to be thread-safe. + * Handler that gets notified while parsing content with {@link ContentParser}. + * The resources are always reported in order of their paths as found in the content fragment. + * Parents are always reported before their children. */ -public interface ContentParser { +public interface ContentHandler { /** - * Parse content. - * @param is Stream with serialized content - * @return Content as Map - * @throws IOException When I/O error occurs. - * @throws ParseException When parsing error occurs. + * Resource found in parsed content. + * @param path Path of resource inside the content fragment. The root resource from the content fragment has a path "/". + * @param properties Resource properties */ - Map<String,Object> parse(InputStream is) throws IOException, ParseException; - + void resource(String path, Map<String,Object> properties); + } diff --git a/src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java b/src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java index 1d9595a..fa6877d 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/ContentParser.java @@ -20,7 +20,6 @@ package org.apache.sling.jcr.contentparser; import java.io.IOException; import java.io.InputStream; -import java.util.Map; /** * Parses repository content from a file. @@ -29,12 +28,12 @@ import java.util.Map; public interface ContentParser { /** - * Parse content. - * @param is Stream with serialized content - * @return Content as Map + * Parse content in a "stream-based" way. Each resource that is found in the content is reported to the contentHandler. + * @param contentHandler Content handler that accepts the parsed content. + * @param inputStream Stream with serialized content * @throws IOException When I/O error occurs. * @throws ParseException When parsing error occurs. */ - Map<String,Object> parse(InputStream is) throws IOException, ParseException; + void parse(ContentHandler contentHandler, InputStream inputStream) throws IOException, ParseException; } diff --git a/src/main/java/org/apache/sling/jcr/contentparser/ContentParserFactory.java b/src/main/java/org/apache/sling/jcr/contentparser/ContentParserFactory.java index 7ae64e2..7357ec5 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/ContentParserFactory.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/ContentParserFactory.java @@ -22,7 +22,7 @@ import org.apache.sling.jcr.contentparser.impl.JcrXmlContentParser; import org.apache.sling.jcr.contentparser.impl.JsonContentParser; /** - * Factory for content file parsers. + * Factory for content parsers. */ public final class ContentParserFactory { diff --git a/src/main/java/org/apache/sling/jcr/contentparser/ContentType.java b/src/main/java/org/apache/sling/jcr/contentparser/ContentType.java index 167722f..acba05d 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/ContentType.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/ContentType.java @@ -29,7 +29,7 @@ public enum ContentType { JSON("json"), /** - * JCR XML content. + * JCR XML content (FileVault XML). */ JCR_XML("jcr.xml"); diff --git a/src/main/java/org/apache/sling/jcr/contentparser/ParserOptions.java b/src/main/java/org/apache/sling/jcr/contentparser/ParserOptions.java index 0911dac..4dba088 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/ParserOptions.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/ParserOptions.java @@ -24,7 +24,7 @@ import java.util.HashSet; import java.util.Set; /** - * Options for content filer parser. + * Options for content parser. */ public final class ParserOptions { @@ -60,7 +60,7 @@ public final class ParserOptions { } /** - * Some content file formats like JSON do not contain information to identify date/time values. + * Some content formats like JSON do not contain information to identify date/time values. * Instead they have to be detected by heuristics by trying to parse every string value. * This mode is disabled by default. * @param value Activate calendar value detection diff --git a/src/main/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParser.java b/src/main/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParser.java index 2af30e7..9b9486c 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParser.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParser.java @@ -20,16 +20,20 @@ package org.apache.sling.jcr.contentparser.impl; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.HashSet; import java.util.Map; -import java.util.Stack; +import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; +import org.apache.commons.lang3.StringUtils; import org.apache.jackrabbit.util.ISO9075; +import org.apache.sling.jcr.contentparser.ContentHandler; import org.apache.sling.jcr.contentparser.ContentParser; import org.apache.sling.jcr.contentparser.ParseException; import org.apache.sling.jcr.contentparser.ParserOptions; @@ -54,15 +58,14 @@ public final class JcrXmlContentParser implements ContentParser { } @Override - public Map<String,Object> parse(InputStream is) throws IOException, ParseException { + public void parse(ContentHandler handler, InputStream is) throws IOException, ParseException { try { - XmlHandler xmlHandler = new XmlHandler(); + XmlHandler xmlHandler = new XmlHandler(handler); SAXParser parser = saxParserFactory.newSAXParser(); parser.parse(is, xmlHandler); if (xmlHandler.hasError()) { throw xmlHandler.getError(); } - return xmlHandler.getContent(); } catch (ParserConfigurationException | SAXException ex) { throw new ParseException("Error parsing JCR XML content.", ex); @@ -82,12 +85,13 @@ public final class JcrXmlContentParser implements ContentParser { * Parses XML stream to Map. */ class XmlHandler extends DefaultHandler { - private final Map<String,Object> content = new LinkedHashMap<>(); - private final Stack<Map<String,Object>> elements = new Stack<>(); + private final ContentHandler contentHandler; + private final Deque<String> paths = new ArrayDeque<>(); + private final Set<String> ignoredPaths = new HashSet<>(); private SAXParseException error; - public Map<String,Object> getContent() { - return content; + public XmlHandler(ContentHandler contentHandler) { + this.contentHandler = contentHandler; } public boolean hasError() { @@ -102,36 +106,55 @@ public final class JcrXmlContentParser implements ContentParser { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - // prepare map for element - Map<String,Object> element; - if (elements.isEmpty()) { - element = content; + String resourceName = decodeName(qName); + + // generate path for element + String path; + if (paths.isEmpty()) { + path = "/"; } else { - element = new HashMap<>(); - String resourceName = decodeName(qName); - if (!helper.ignoreResource(resourceName)) { - elements.peek().put(resourceName, element); + path = helper.concatenatePath(paths.peek(), resourceName); + if (helper.ignoreResource(resourceName)) { + ignoredPaths.add(path); } } - elements.push(element); + paths.push(path); + + // skip further processing if this path or a parent path is ignored + if (isIgnoredPath(path)) { + return; + } - // get attributes + // get properties + Map<String,Object> properties = new HashMap<>(); for (int i=0; i<attributes.getLength(); i++) { String propertyName = helper.cleanupPropertyName(decodeName(attributes.getQName(i))); if (!helper.ignoreProperty(propertyName)) { Object value = JcrXmlValueConverter.parseValue(propertyName, attributes.getValue(i)); if (value != null) { - element.put(propertyName, value); + properties.put(propertyName, value); } } } + helper.ensureDefaultPrimaryType(properties); + contentHandler.resource(path, properties); + } + + private boolean isIgnoredPath(String path) { + if (StringUtils.isEmpty(path)) { + return false; + } + if (ignoredPaths.contains(path)) { + return true; + } + String parentPath = StringUtils.substringBeforeLast(path, "/"); + return isIgnoredPath(parentPath); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { - Map<String,Object> element = elements.pop(); - helper.ensureDefaultPrimaryType(element); + paths.pop(); } @Override diff --git a/src/main/java/org/apache/sling/jcr/contentparser/impl/JsonContentParser.java b/src/main/java/org/apache/sling/jcr/contentparser/impl/JsonContentParser.java index a17e91e..093fbec 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/impl/JsonContentParser.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/impl/JsonContentParser.java @@ -35,6 +35,7 @@ import javax.json.JsonString; import javax.json.JsonValue; import javax.json.stream.JsonParsingException; +import org.apache.sling.jcr.contentparser.ContentHandler; import org.apache.sling.jcr.contentparser.ContentParser; import org.apache.sling.jcr.contentparser.ParseException; import org.apache.sling.jcr.contentparser.ParserOptions; @@ -45,7 +46,12 @@ import org.apache.sling.jcr.contentparser.ParserOptions; */ public final class JsonContentParser implements ContentParser { - private final ParserHelper helper; + private final ParserHelper helper; + /* + * Implementation note: This parser uses JsonReader instead of the (more memory-efficient) + * JsonParser Stream API because otherwise it would not be possible to report parent resources + * including all properties properly before their children. + */ private final JsonReaderFactory jsonReaderFactory; public JsonContentParser(ParserOptions options) { @@ -57,22 +63,25 @@ public final class JsonContentParser implements ContentParser { } @Override - public Map<String,Object> parse(InputStream is) throws IOException, ParseException { + public void parse(ContentHandler handler, InputStream is) throws IOException, ParseException { try (JsonReader reader = jsonReaderFactory.createReader(is)) { - return toMap(reader.readObject()); + parse(handler, reader.readObject(), "/"); } catch (JsonParsingException ex) { throw new ParseException("Error parsing JSON content.", ex); } } - - private Map<String,Object> toMap(JsonObject object) { - Map<String,Object> map = new LinkedHashMap<>(); + + private void parse(ContentHandler handler, JsonObject object, String path) { + // parse JSON object + Map<String,Object> properties = new HashMap<>(); + Map<String,JsonObject> children = new LinkedHashMap<>(); for (Map.Entry<String, JsonValue> entry : object.entrySet()) { String childName = entry.getKey(); Object value = convertValue(entry.getValue()); + boolean isResource = (value instanceof JsonObject); boolean ignore = false; - if (value instanceof Map) { + if (isResource) { ignore = helper.ignoreResource(childName); } else { @@ -80,11 +89,24 @@ public final class JsonContentParser implements ContentParser { ignore = helper.ignoreProperty(childName); } if (!ignore) { - map.put(childName, value); + if (isResource) { + children.put(childName, (JsonObject)value); + } + else { + properties.put(childName, value); + } } } - helper.ensureDefaultPrimaryType(map); - return map; + helper.ensureDefaultPrimaryType(properties); + + // report current JSON object + handler.resource(path, properties); + + // parse and report children + for (Map.Entry<String,JsonObject> entry : children.entrySet()) { + String childPath = helper.concatenatePath(path, entry.getKey());; + parse(handler, entry.getValue(), childPath); + } } private Object convertValue(JsonValue value) { @@ -120,7 +142,7 @@ public final class JsonContentParser implements ContentParser { } return helper.convertSingleTypeArray(values); case OBJECT: - return toMap((JsonObject)value); + return (JsonObject)value; default: throw new ParseException("Unexpected JSON value type: " + value.getValueType()); } diff --git a/src/main/java/org/apache/sling/jcr/contentparser/impl/ParserHelper.java b/src/main/java/org/apache/sling/jcr/contentparser/impl/ParserHelper.java index 160cd0e..69b0f48 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/impl/ParserHelper.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/impl/ParserHelper.java @@ -27,6 +27,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.json.JsonObject; + import org.apache.commons.lang3.StringUtils; import org.apache.sling.jcr.contentparser.ParseException; import org.apache.sling.jcr.contentparser.ParserOptions; @@ -113,7 +115,7 @@ class ParserHelper { if (value == null) { throw new ParseException("Multivalue array must not contain null values."); } - if (value instanceof Map) { + if (value instanceof Map || value instanceof JsonObject) { throw new ParseException("Multivalue array must not contain maps/objects."); } if (itemType == null) { @@ -131,4 +133,13 @@ class ParserHelper { return convertedArray; } + public String concatenatePath(String parentPath, String name) { + if (StringUtils.endsWith(parentPath, "/")) { + return parentPath + name; + } + else { + return parentPath + "/" + name; + } + } + } diff --git a/src/main/java/org/apache/sling/jcr/contentparser/package-info.java b/src/main/java/org/apache/sling/jcr/contentparser/package-info.java index 21b7f41..1b6b2a8 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/package-info.java +++ b/src/main/java/org/apache/sling/jcr/contentparser/package-info.java @@ -17,7 +17,7 @@ * under the License. */ /** - * Parser for repository content stored in files (e.g. JSON, JCR XML). + * Parser for repository content serialized e.g. as JSON or JCR XML. */ @org.osgi.annotation.versioning.Version("1.0.0") package org.apache.sling.jcr.contentparser; diff --git a/src/test/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParserTest.java b/src/test/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParserTest.java index 1faaf4e..99eca51 100644 --- a/src/test/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParserTest.java +++ b/src/test/java/org/apache/sling/jcr/contentparser/impl/JcrXmlContentParserTest.java @@ -18,7 +18,6 @@ */ package org.apache.sling.jcr.contentparser.impl; -import static org.apache.sling.jcr.contentparser.impl.TestUtils.getDeep; import static org.apache.sling.jcr.contentparser.impl.TestUtils.parse; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -37,8 +36,8 @@ import org.apache.sling.jcr.contentparser.ContentParserFactory; import org.apache.sling.jcr.contentparser.ContentType; import org.apache.sling.jcr.contentparser.ParseException; import org.apache.sling.jcr.contentparser.ParserOptions; +import org.apache.sling.jcr.contentparser.impl.mapsupport.ContentElement; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import com.google.common.collect.ImmutableSet; @@ -52,14 +51,13 @@ public class JcrXmlContentParserTest { file = new File("src/test/resources/content-test/content.jcr.xml"); } - @SuppressWarnings("unchecked") @Test public void testParseJcrXml() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JCR_XML); - Map<String,Object> content = parse(underTest, file); + ContentElement content = parse(underTest, 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(expected=ParseException.class) @@ -72,8 +70,8 @@ public class JcrXmlContentParserTest { @Test public void testDataTypes() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JCR_XML); - Map<String,Object> content = parse(underTest, file); - Map<String,Object> props = getDeep(content, "jcr:content"); + ContentElement content = parse(underTest, file); + Map<String,Object> props = content.getChild("jcr:content").getProperties(); assertEquals("en", props.get("jcr:title")); assertEquals(true, props.get("includeAside")); @@ -105,26 +103,43 @@ public class JcrXmlContentParserTest { ContentParser underTest = ContentParserFactory.create(ContentType.JCR_XML, new ParserOptions() .ignoreResourceNames(ImmutableSet.of("teaserbar", "aside")) .ignorePropertyNames(ImmutableSet.of("longProp", "jcr:title"))); - Map<String,Object> content = parse(underTest, file); - Map<String,Object> props = getDeep(content, "jcr:content"); + ContentElement content = parse(underTest, file); + ContentElement child = content.getChild("jcr:content"); - assertEquals("HOME", props.get("navTitle")); - assertNull(props.get("jcr:title")); - assertNull(props.get("longProp")); + assertEquals("HOME", child.getProperties().get("navTitle")); + assertNull(child.getProperties().get("jcr:title")); + assertNull(child.getProperties().get("longProp")); - assertNull(props.get("teaserbar")); - assertNull(props.get("aside")); - assertNotNull(props.get("content")); + assertNull(child.getChildren().get("teaserbar")); + assertNull(child.getChildren().get("aside")); + assertNotNull(child.getChildren().get("content")); + } + + @Test + public void testGetChild() throws Exception { + ContentParser underTest = ContentParserFactory.create(ContentType.JCR_XML); + ContentElement content = parse(underTest, file); + assertNull(content.getName()); + + ContentElement deepChild = content.getChild("jcr:content/teaserbar/teaserbaritem"); + assertEquals("teaserbaritem", deepChild.getName()); + assertEquals("samples/sample-app/components/content/teaserbar/teaserbarItem", deepChild.getProperties().get("sling:resourceType")); + + ContentElement invalidChild = content.getChild("non/existing/path"); + assertNull(invalidChild); + + invalidChild = content.getChild("/jcr:content"); + assertNull(invalidChild); } @Test - @Ignore public void testSameNamePropertyAndSubResource() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JCR_XML); - Map<String,Object> content = parse(underTest, file); - Map<String,Object> props = getDeep(content, "jcr:content/teaserbar"); + ContentElement content = parse(underTest, file); + ContentElement child = content.getChild("jcr:content/teaserbar"); // teaserbaritem is a direct property as well as a sub resource - assertEquals("test", props.get("teaserbaritem")); + assertEquals("test", child.getProperties().get("teaserbaritem")); + assertNotNull(child.getChildren().get("teaserbaritem")); } } diff --git a/src/test/java/org/apache/sling/jcr/contentparser/impl/JsonContentParserTest.java b/src/test/java/org/apache/sling/jcr/contentparser/impl/JsonContentParserTest.java index 3aa9531..706cb6e 100644 --- a/src/test/java/org/apache/sling/jcr/contentparser/impl/JsonContentParserTest.java +++ b/src/test/java/org/apache/sling/jcr/contentparser/impl/JsonContentParserTest.java @@ -18,7 +18,6 @@ */ package org.apache.sling.jcr.contentparser.impl; -import static org.apache.sling.jcr.contentparser.impl.TestUtils.getDeep; import static org.apache.sling.jcr.contentparser.impl.TestUtils.parse; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -36,6 +35,7 @@ import org.apache.sling.jcr.contentparser.ContentParserFactory; import org.apache.sling.jcr.contentparser.ContentType; import org.apache.sling.jcr.contentparser.ParseException; import org.apache.sling.jcr.contentparser.ParserOptions; +import org.apache.sling.jcr.contentparser.impl.mapsupport.ContentElement; import org.junit.Before; import org.junit.Test; @@ -53,17 +53,17 @@ public class JsonContentParserTest { @Test public void testPageJcrPrimaryType() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JSON); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); - assertEquals("app:Page", content.get("jcr:primaryType")); + assertEquals("app:Page", content.getProperties().get("jcr:primaryType")); } @Test public void testDataTypes() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JSON); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); - Map<String, Object> props = getDeep(content, "toolbar/profiles/jcr:content"); + Map<String, Object> props = content.getChild("toolbar/profiles/jcr:content").getProperties(); assertEquals(true, props.get("hideInNav")); assertEquals(1234567890123L, props.get("longProp")); @@ -78,9 +78,9 @@ public class JsonContentParserTest { @Test public void testContentProperties() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JSON); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); - Map<String, Object> props = getDeep(content, "jcr:content/header"); + Map<String, Object> props = content.getChild("jcr:content/header").getProperties(); assertEquals("/content/dam/sample/header.png", props.get("imageReference")); } @@ -88,9 +88,9 @@ public class JsonContentParserTest { public void testCalendar() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JSON, new ParserOptions().detectCalendarValues(true)); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); - Map<String, Object> props = getDeep(content, "jcr:content"); + Map<String, Object> props = content.getChild("jcr:content").getProperties(); Calendar calendar = (Calendar) props.get("app:lastModified"); assertNotNull(calendar); @@ -109,9 +109,9 @@ public class JsonContentParserTest { @Test public void testUTF8Chars() throws Exception { ContentParser underTest = ContentParserFactory.create(ContentType.JSON); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); - Map<String, Object> props = getDeep(content, "jcr:content"); + Map<String, Object> props = content.getChild("jcr:content").getProperties(); assertEquals("äöü߀", props.get("utf8Property")); } @@ -120,7 +120,7 @@ public class JsonContentParserTest { public void testParseInvalidJson() throws Exception { file = new File("src/test/resources/invalid-test/invalid.json"); ContentParser underTest = ContentParserFactory.create(ContentType.JSON); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); assertNull(content); } @@ -128,7 +128,7 @@ public class JsonContentParserTest { public void testParseInvalidJsonWithObjectList() throws Exception { file = new File("src/test/resources/invalid-test/contentWithObjectList.json"); ContentParser underTest = ContentParserFactory.create(ContentType.JSON); - Map<String, Object> content = parse(underTest, file); + ContentElement content = parse(underTest, file); assertNull(content); } @@ -137,18 +137,35 @@ public class JsonContentParserTest { ContentParser underTest = ContentParserFactory.create(ContentType.JSON, new ParserOptions().ignoreResourceNames(ImmutableSet.of("header", "newslist")) .ignorePropertyNames(ImmutableSet.of("jcr:title"))); - Map<String, Object> content = parse(underTest, file); - Map<String, Object> props = getDeep(content, "jcr:content"); + ContentElement content = parse(underTest, file); + ContentElement child = content.getChild("jcr:content"); - assertEquals("Sample Homepage", props.get("pageTitle")); - assertNull(props.get("jcr:title")); + assertEquals("Sample Homepage", child.getProperties().get("pageTitle")); + assertNull(child.getProperties().get("jcr:title")); - assertNull(props.get("header")); - assertNull(props.get("newslist")); - assertNotNull(props.get("lead")); + assertNull(child.getChildren().get("header")); + assertNull(child.getChildren().get("newslist")); + assertNotNull(child.getChildren().get("lead")); - assertEquals("abc", props.get("refpro1")); - assertEquals("def", props.get("pathprop1")); + assertEquals("abc", child.getProperties().get("refpro1")); + assertEquals("def", child.getProperties().get("pathprop1")); + } + + @Test + public void testGetChild() throws Exception { + ContentParser underTest = ContentParserFactory.create(ContentType.JSON); + 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/java/org/apache/sling/jcr/contentparser/impl/TestUtils.java b/src/test/java/org/apache/sling/jcr/contentparser/impl/TestUtils.java index f91c5ee..be395b9 100644 --- a/src/test/java/org/apache/sling/jcr/contentparser/impl/TestUtils.java +++ b/src/test/java/org/apache/sling/jcr/contentparser/impl/TestUtils.java @@ -22,10 +22,10 @@ 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; +import org.apache.sling.jcr.contentparser.impl.mapsupport.ContentElement; +import org.apache.sling.jcr.contentparser.impl.mapsupport.ContentElementHandler; public final class TestUtils { @@ -33,27 +33,12 @@ public final class TestUtils { // static methods only } - @SuppressWarnings("unchecked") - public static Map<String, Object> getDeep(Map<String, Object> map, String path) { - String name = StringUtils.substringBefore(path, "/"); - Object object = map.get(name); - if (object == null || !(object instanceof Map)) { - return null; - } - String remainingPath = StringUtils.substringAfter(path, "/"); - Map<String, Object> childMap = (Map<String, Object>)object; - if (StringUtils.isEmpty(remainingPath)) { - return childMap; - } - else { - return getDeep(childMap, remainingPath); - } - } - - public static Map<String,Object> parse(ContentParser contentParser, File file) throws IOException { + public 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/main/java/org/apache/sling/jcr/contentparser/ContentType.java b/src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElement.java similarity index 50% copy from src/main/java/org/apache/sling/jcr/contentparser/ContentType.java copy to src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElement.java index 167722f..6584487 100644 --- a/src/main/java/org/apache/sling/jcr/contentparser/ContentType.java +++ b/src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElement.java @@ -16,35 +16,37 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.sling.jcr.contentparser; +package org.apache.sling.jcr.contentparser.impl.mapsupport; + +import java.util.Map; /** - * Content types. + * Represents a resource or node in the content hierarchy. */ -public enum ContentType { +public interface ContentElement { /** - * JSON content. + * @return Resource name. The root resource has no name (null). */ - JSON("json"), - + String getName(); + /** - * JCR XML content. + * Properties of this resource. + * @return Properties (keys, values) */ - JCR_XML("jcr.xml"); - - - private final String extension; - - private ContentType(String extension) { - this.extension = extension; - } - + Map<String, Object> getProperties(); + /** - * @return Extension + * Get children of current resource. The Map preserves the ordering of children. + * @return Children (child names, child objects) */ - public String getExtension() { - return extension; - } - + 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/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElementHandler.java b/src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElementHandler.java new file mode 100644 index 0000000..190adad --- /dev/null +++ b/src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/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.jcr.contentparser.impl.mapsupport; + +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. + */ +public 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/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElementImpl.java b/src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/ContentElementImpl.java new file mode 100644 index 0000000..3956c98 --- /dev/null +++ b/src/test/java/org/apache/sling/jcr/contentparser/impl/mapsupport/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.jcr.contentparser.impl.mapsupport; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +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); + } + } + +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
