blob: 4685a8cbba4bf66eb5d0a631e59e04de57ce77d2 [file] [log] [blame]
/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.PropertyType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.jcr.contentparser.ContentHandler;
import org.apache.sling.jcr.contentparser.ContentParser;
import org.apache.sling.jcr.contentparser.ParseException;
import org.apache.sling.jcr.contentparser.ParserOptions;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Parses XML files that contains content fragments.
* Instance of this class is thread-safe.
*/
public final class XmlContentParser implements ContentParser {
private final ParserHelper helper;
private final DocumentBuilderFactory documentBuilderFactory;
public XmlContentParser(ParserOptions options) {
this.helper = new ParserHelper(options);
documentBuilderFactory = DocumentBuilderFactory.newInstance();
}
@Override
public void parse(ContentHandler handler, InputStream is) throws IOException, ParseException {
try {
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.parse(is);
parse(handler, doc.getDocumentElement(), null);
}
catch (ParserConfigurationException | SAXException ex) {
throw new ParseException("Error parsing JCR XML content.", ex);
}
}
private void parse(ContentHandler handler, Element element, String parentPath) {
// build node path
String path;
if (parentPath == null) {
path = "/";
}
else {
String name = getChildText(element, "name");
if (StringUtils.isEmpty(name)) {
throw new ParseException("Child node without name detected below path " + parentPath);
}
path = helper.concatenatePath(parentPath, name);
if (helper.ignoreResource(name)) {
return;
}
}
Map<String,Object> properties = new HashMap<>();
// primary node type and mixins
String primaryType = getChildText(element, "primaryNodeType");
if (StringUtils.isNotBlank(primaryType) && !helper.ignoreProperty("jcr:primaryType")) {
properties.put("jcr:primaryType", primaryType);
}
String[] mixins = getChildTextArray(element, "mixinNodeType");
if (mixins.length > 0 && !helper.ignoreProperty("jcr:mixinTypes")) {
properties.put("jcr:mixinTypes", mixins);
}
// properties
List<Element> propertyElements = getChildren(element, "property");
for (Element propertyElement : propertyElements) {
// property name
String name = getChildText(propertyElement, "name");
if (StringUtils.isBlank(name)) {
throw new ParseException("Property without name detected at path " + path);
}
if (helper.ignoreProperty(name)) {
continue;
}
// property type
String typeString = getChildText(propertyElement, "type");
if (StringUtils.isBlank(typeString)) {
throw new ParseException("Property '" + name + "' has no type at path " + path);
}
int type;
try {
type = PropertyType.valueFromName(typeString);
}
catch (IllegalArgumentException ex) {
throw new ParseException("Property '" + name + "' has illegal type '" + typeString + "' at path " + path);
}
// property value
Object value;
List<Element> valuesElements = getChildren(propertyElement, "values");
if (!valuesElements.isEmpty()) {
Element valuesElement = valuesElements.get(0);
List<Element> valueElements = getChildren(valuesElement, "value");
String[] stringValues = new String[valueElements.size()];
for (int i=0; i<valueElements.size(); i++) {
stringValues[i] = valueElements.get(i).getTextContent();
}
value = convertMultiValue(stringValues, type);
}
else {
String stringValue = getChildText(propertyElement, "value");
value = convertValue(stringValue, type);
}
properties.put(name, value);
}
// report current JSON object
helper.ensureDefaultPrimaryType(properties);
handler.resource(path, properties);
// child nodes
List<Element> nodeElements = getChildren(element, "node");
for (Element node : nodeElements) {
parse(handler, node, path);
}
}
private List<Element> getChildren(Element element, String childName) {
List<Element> result = new ArrayList<>();
NodeList children = element.getChildNodes();
int len = children.getLength();
for (int i=0; i<len; i++) {
Node child = children.item(i);
if (child instanceof Element) {
Element childElement = (Element)child;
if (StringUtils.equals(childElement.getNodeName(), childName)) {
result.add(childElement);
}
}
}
return result;
}
private String getChildText(Element element, String childName) {
List<Element> children = getChildren(element, childName);
if (children.isEmpty()) {
return null;
}
else if (children.size() == 1) {
return children.get(0).getTextContent();
}
else {
throw new ParseException("Found multiple elements with name '" + childName + "': " + children.size());
}
}
private String[] getChildTextArray(Element element, String childName) {
List<Element> children = getChildren(element, childName);
String[] result = new String[children.size()];
for (int i=0; i<children.size(); i++) {
result[i] = children.get(i).getTextContent();
}
return result;
}
private Object convertValue(String value, int type) {
switch (type) {
case PropertyType.STRING:
case PropertyType.NAME:
case PropertyType.PATH:
case PropertyType.REFERENCE:
case PropertyType.WEAKREFERENCE:
case PropertyType.URI:
return value;
case PropertyType.LONG:
return Long.valueOf(value);
case PropertyType.DOUBLE:
return Double.valueOf(value);
case PropertyType.DATE:
return helper.tryParseCalendar(value);
case PropertyType.BOOLEAN:
return Boolean.valueOf(value);
case PropertyType.DECIMAL:
return new BigDecimal(value);
default:
throw new ParseException("Unsupported property type: " + PropertyType.nameFromValue(type));
}
}
private Object convertMultiValue(String[] values, int type) {
Object[] result = new Object[values.length];
for (int i=0; i<values.length; i++) {
result[i] = convertValue(values[i], type);
}
return helper.convertSingleTypeArray(result);
}
}