blob: 55640fc8aedc2833fca3617468bf4d674d15f842 [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.ide.impl.vlt.serialization;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.util.Calendar;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
import javax.jcr.NamespaceException;
import javax.jcr.PropertyType;
import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.apache.jackrabbit.util.ISO8601;
import org.apache.jackrabbit.util.ISO9075;
import org.apache.jackrabbit.vault.util.DocViewNode;
import org.apache.jackrabbit.vault.util.DocViewProperty;
import org.apache.sling.ide.log.Logger;
import org.apache.sling.ide.transport.ResourceProxy;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class ContentXmlHandler extends DefaultHandler implements NamespaceResolver {
private static final String JCR_ROOT = "jcr:root";
private final ResourceProxy root;
private final Deque<ResourceProxy> queue = new LinkedList<>();
/**
* map containing fully qualified uris as keys and their defined prefixes as values
*/
private final Map<String, String> uriPrefixMap;
/**
* the default name path resolver
*/
private final DefaultNamePathResolver npResolver = new DefaultNamePathResolver(this);
private Logger logger;
/**
* all type hint classes in a map (key = type integer value)
*/
private static final Map<Integer, TypeHint> TYPE_HINT_MAP;
static {
TYPE_HINT_MAP = new HashMap<>();
for (TypeHint hint : EnumSet.allOf(TypeHint.class)) {
TYPE_HINT_MAP.put(hint.propertyType, hint);
}
}
public ContentXmlHandler(String rootResourcePath, Logger logger) {
root = new ResourceProxy(rootResourcePath);
uriPrefixMap = new HashMap<>();
this.logger = logger;
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
uriPrefixMap.put(uri, prefix);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
ResourceProxy current;
// name is equal to label except for SNS
String label = ISO9075.decode(qName);
String name = label;
// code mostly taken from {@link org.apache.jackrabbit.vault.fs.impl.io.DocViewSaxImporter}
DocViewNode node;
try {
node = new DocViewNode(name, label, attributes, npResolver);
if (qName.equals(JCR_ROOT)) {
current = root;
} else {
ResourceProxy parent = queue.peekLast();
StringBuilder path = new StringBuilder(parent.getPath());
if (path.charAt(path.length() - 1) != '/')
path.append('/');
path.append(qName);
current = new ResourceProxy(ISO9075.decode(path.toString()));
parent.addChild(current);
}
for (Map.Entry<String, DocViewProperty> entry : node.props.entrySet()) {
try {
Object typedValue = TypeHint.convertDocViewPropertyToTypedValue(entry.getValue());
// unsupported
if (typedValue == null) {
continue;
}
current.addProperty(entry.getKey(), typedValue);
} catch (Throwable t) {
logger.error("Could not parse property '" + entry.getValue().name, t);
}
}
queue.add(current);
} catch (NamespaceException e) {
logger.error("Could not resolve a JCR namespace.", e);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
queue.removeLast();
}
public ResourceProxy getRoot() {
return root;
}
/**
* Each enum implements the {@link TypeHint#parseValues(String[], boolean)} in a way, that the String[] value is converted to the closest underlying type.
*/
static enum TypeHint {
UNDEFINED(PropertyType.UNDEFINED) {
Object parseValues(String[] values, boolean explicitMultiValue) {
return STRING.parseValues(values, explicitMultiValue);
}
},
STRING(PropertyType.STRING) {
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return values[0];
} else {
return values;
}
}
},
BINARY(PropertyType.BINARY) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
return null;
}
},
BOOLEAN(PropertyType.BOOLEAN) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return Boolean.valueOf(values[0]);
}
Boolean[] ret = new Boolean[values.length];
for (int i = 0; i < values.length; i++) {
ret[i] = Boolean.parseBoolean(values[i]);
}
return ret;
}
},
DATE(PropertyType.DATE) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return ISO8601.parse(values[0]);
}
Calendar[] ret = new Calendar[values.length];
for (int i = 0; i < values.length; i++) {
ret[i] = ISO8601.parse(values[i]);
}
return ret;
}
},
DOUBLE(PropertyType.DOUBLE) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return Double.parseDouble(values[0]);
}
Double[] ret = new Double[values.length];
for (int i = 0; i < values.length; i++) {
ret[i] = Double.parseDouble(values[i]);
}
return ret;
}
},
LONG(PropertyType.LONG) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return Long.valueOf(values[0]);
}
Long[] ret = new Long[values.length];
for ( int i =0 ; i < values.length; i++ ) {
ret[i] = Long.valueOf(values[i]);
}
return ret;
}
},
DECIMAL(PropertyType.DECIMAL) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return new BigDecimal(values[0]);
}
BigDecimal[] ret = new BigDecimal[values.length];
for ( int i = 0; i < values.length; i++) {
ret[i] = new BigDecimal(values[i]);
}
return ret;
}
},
NAME(PropertyType.NAME) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return values[0];
}
return values;
}
},
PATH(PropertyType.PATH) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
return NAME.parseValues(values, explicitMultiValue);
}
},
REFERENCE(PropertyType.REFERENCE) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
if (values.length == 1 && !explicitMultiValue) {
return UUID.fromString(values[0]);
}
UUID[] refs = new UUID[values.length];
for (int i = 0; i < values.length; i++) {
String value = values[i];
refs[i] = UUID.fromString(value);
}
return refs;
}
},
WEAKREFERENCE(PropertyType.WEAKREFERENCE) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
return REFERENCE.parseValues(values, explicitMultiValue);
}
},
URI(PropertyType.URI) {
@Override
Object parseValues(String[] values, boolean explicitMultiValue) {
try {
if (values.length == 1 && !explicitMultiValue) {
return new java.net.URI(values[0]);
}
java.net.URI[] refs = new java.net.URI[values.length];
for (int i = 0; i < values.length; i++) {
String value = values[i];
refs[i] = new java.net.URI(value);
}
return refs;
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Given value cannot be converted to URI", e);
}
}
};
static Object convertDocViewPropertyToTypedValue(DocViewProperty property) {
TypeHint hint = TYPE_HINT_MAP.get(property.type);
if (hint == null) {
throw new IllegalArgumentException("Unknown type value '" + property.type + "'");
}
return hint.parseValues(property.values, property.isMulti);
}
private final int propertyType;
/**
*
* @param propertyType one of type values being defined in {@link javax.jcr.PropertyType}
*/
private TypeHint(int propertyType) {
this.propertyType = propertyType;
}
abstract Object parseValues(String[] values, boolean explicitMultiValue);
}
/**
* {@inheritDoc}
*/
public String getURI(String prefix) throws NamespaceException {
throw new UnsupportedOperationException("The method getUri is not implemented as this is not being used");
}
/**
* {@inheritDoc}
*/
public String getPrefix(String uri) throws NamespaceException {
String prefix = uriPrefixMap.get(uri);
if (prefix == null) {
throw new NamespaceException("Could not find defined prefix for uri " + uri);
}
return prefix;
}
}