Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SysViewImportHandler.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SysViewImportHandler.java?rev=1453985&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SysViewImportHandler.java (added) +++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SysViewImportHandler.java Thu Mar 7 18:18:19 2013 @@ -0,0 +1,362 @@ +/* + * 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.jackrabbit.oak.jcr.xml; + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; +import javax.jcr.InvalidSerializedDataException; +import javax.jcr.NamespaceRegistry; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.ValueFactory; + +import org.apache.jackrabbit.commons.NamespaceHelper; +import org.apache.jackrabbit.oak.plugins.name.NamespaceConstants; +import org.apache.jackrabbit.oak.spi.xml.NodeInfo; +import org.apache.jackrabbit.oak.spi.xml.PropInfo; +import org.apache.jackrabbit.oak.spi.xml.TextValue; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +/** + * <code>SysViewImportHandler</code> ... + */ +class SysViewImportHandler extends TargetImportHandler { + + /** + * stack of ImportState instances; an instance is pushed onto the stack + * in the startElement method every time a sv:node element is encountered; + * the same instance is popped from the stack in the endElement method + * when the corresponding sv:node element is encountered. + */ + private final Stack<ImportState> stack = new Stack<ImportState>(); + + /** + * fields used temporarily while processing sv:property and sv:value elements + */ + private NameInfo currentPropName; + private int currentPropType = PropertyType.UNDEFINED; + private PropInfo.MultipleStatus currentPropMultipleStatus = PropInfo.MultipleStatus.UNKNOWN; + // list of appendable value objects + private ArrayList<BufferedStringValue> currentPropValues = + new ArrayList<BufferedStringValue>(); + private BufferedStringValue currentPropValue; + + /** + * Constructs a new <code>SysViewImportHandler</code>. + * + * @param importer the underlying importer + * @param valueFactory the value factory + */ + SysViewImportHandler(Importer importer, ValueFactory valueFactory, NamespaceHelper helper) { + super(importer, valueFactory, helper); + } + + private void processNode(ImportState state, boolean start, boolean end) + throws SAXException { + if (!start && !end) { + return; + } + String[] mixinNames = null; + if (state.mixinNames != null) { + mixinNames = state.mixinNames.toArray( + new String[state.mixinNames.size()]); + } + String id = state.uuid; + NodeInfo node = + new NodeInfo(state.nodeName, state.nodeTypeName, mixinNames, id); + // call Importer + try { + if (start) { + importer.startNode(node, state.props); + // dispose temporary property values + for (PropInfo pi : state.props) { + pi.dispose(); + } + + } + if (end) { + importer.endNode(node); + } + } catch (RepositoryException re) { + throw new SAXException(re); + } + } + + //-------------------------------------------------------< ContentHandler > + + /** + * {@inheritDoc} + */ + @Override + public void startElement(String namespaceURI, String localName, + String qName, Attributes atts) + throws SAXException { + // check element name + if (namespaceURI.equals(NamespaceConstants.NAMESPACE_SV) && "node".equals(localName)) { + // sv:node element + + // node name (value of sv:name attribute) + String svName = getAttribute(atts, NamespaceConstants.NAMESPACE_SV, "name"); + if (svName == null) { + throw new SAXException(new InvalidSerializedDataException( + "missing mandatory sv:name attribute of element sv:node")); + } + + if (!stack.isEmpty()) { + // process current node first + ImportState current = stack.peek(); + // need to start current node + if (!current.started) { + processNode(current, true, false); + current.started = true; + } + } + + // push new ImportState instance onto the stack + ImportState state = new ImportState(); + try { + state.nodeName = new NameInfo(svName).getRepoQualifiedName(); + } catch (RepositoryException e) { + throw new SAXException(new InvalidSerializedDataException("illegal node name: " + svName, e)); + } + stack.push(state); + } else if (namespaceURI.equals(NamespaceConstants.NAMESPACE_SV) && "property".equals(localName)) { + // sv:property element + + // reset temp fields + currentPropValues.clear(); + + // property name (value of sv:name attribute) + String svName = getAttribute(atts, NamespaceConstants.NAMESPACE_SV, "name"); + if (svName == null) { + throw new SAXException(new InvalidSerializedDataException( + "missing mandatory sv:name attribute of element sv:property")); + } + try { + currentPropName = new NameInfo(svName); + } catch (RepositoryException e) { + throw new SAXException(new InvalidSerializedDataException("illegal property name: " + svName, e)); + } + // property type (sv:type attribute) + String type = getAttribute(atts, NamespaceConstants.NAMESPACE_SV, "type"); + if (type == null) { + throw new SAXException(new InvalidSerializedDataException( + "missing mandatory sv:type attribute of element sv:property")); + } + try { + currentPropType = PropertyType.valueFromName(type); + } catch (IllegalArgumentException e) { + throw new SAXException(new InvalidSerializedDataException( + "Unknown property type: " + type, e)); + } + // 'multi-value' hint (sv:multiple attribute) + String multiple = getAttribute(atts, NamespaceConstants.NAMESPACE_SV, "multiple"); + if (multiple != null) { + currentPropMultipleStatus = PropInfo.MultipleStatus.MULTIPLE; + } else { + currentPropMultipleStatus = PropInfo.MultipleStatus.UNKNOWN; + } + } else if (namespaceURI.equals(NamespaceConstants.NAMESPACE_SV) && "value".equals(localName)) { + // sv:value element + currentPropValue = new BufferedStringValue(valueFactory, currentNamePathMapper()); + String xsiType = atts.getValue("xsi:type"); + currentPropValue.setBase64("xs:base64Binary".equals(xsiType)); + } else { + throw new SAXException(new InvalidSerializedDataException( + "Unexpected element in system view xml document: {" + namespaceURI + "}" + localName)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void characters(char[] ch, int start, int length) + throws SAXException { + if (currentPropValue != null) { + // property value (character data of sv:value element) + try { + currentPropValue.append(ch, start, length); + } catch (IOException ioe) { + throw new SAXException("error while processing property value", + ioe); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void ignorableWhitespace(char[] ch, int start, int length) + throws SAXException { + if (currentPropValue != null) { + // property value + + // data reported by the ignorableWhitespace event within + // sv:value tags is considered part of the value + try { + currentPropValue.append(ch, start, length); + } catch (IOException ioe) { + throw new SAXException("error while processing property value", + ioe); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void endElement(String namespaceURI, String localName, String qName) + throws SAXException { + // check element name + ImportState state = stack.peek(); + if (namespaceURI.equals(NamespaceConstants.NAMESPACE_SV) && "node".equals(localName)) { + // sv:node element + if (!state.started) { + // need to start & end current node + processNode(state, true, true); + state.started = true; + } else { + // need to end current node + processNode(state, false, true); + } + // pop current state from stack + stack.pop(); + } else if (namespaceURI.equals(NamespaceConstants.NAMESPACE_SV) && "property".equals(localName)) { + // sv:property element + + // check if all system properties (jcr:primaryType, jcr:uuid etc.) + // have been collected and create node as necessary primaryType + if (currentPropName != null + && currentPropName.getNamespaceUri().equals(NamespaceRegistry.NAMESPACE_JCR) + && currentPropName.getLocalName().equals("primaryType")) { + BufferedStringValue val = currentPropValues.get(0); + String s = null; + try { + s = val.retrieve(); + state.nodeTypeName = new NameInfo(s).getRepoQualifiedName(); + } catch (IOException e) { + throw new SAXException(new InvalidSerializedDataException("illegal node type name: " + s, e)); + } catch (RepositoryException e) { + throw new SAXException(new InvalidSerializedDataException("illegal node type name: " + s, e)); + } + } else if (currentPropName != null + && currentPropName.getNamespaceUri().equals(NamespaceRegistry.NAMESPACE_JCR) + && currentPropName.getLocalName().equals("mixinTypes")) { + if (state.mixinNames == null) { + state.mixinNames = new ArrayList<String>(currentPropValues.size()); + } + for (BufferedStringValue val : currentPropValues) { + String s = null; + try { + s = val.retrieve(); + state.mixinNames.add(new NameInfo(s).getRepoQualifiedName()); + } catch (IOException ioe) { + throw new SAXException("error while retrieving value", ioe); + } catch (RepositoryException e) { + throw new SAXException(new InvalidSerializedDataException("illegal mixin type name: " + s, e)); + } + } + } else if (currentPropName != null + && currentPropName.getNamespaceUri().equals(NamespaceRegistry.NAMESPACE_JCR) + && currentPropName.getLocalName().equals("uuid")) { + BufferedStringValue val = currentPropValues.get(0); + try { + state.uuid = val.retrieve(); + } catch (IOException ioe) { + throw new SAXException("error while retrieving value", ioe); + } + } else { + if (currentPropMultipleStatus == PropInfo.MultipleStatus.UNKNOWN + && currentPropValues.size() != 1) { + currentPropMultipleStatus = PropInfo.MultipleStatus.MULTIPLE; + } + PropInfo prop = new PropInfo( + currentPropName == null ? null : currentPropName.getRepoQualifiedName(), + currentPropType, + currentPropValues.toArray(new TextValue[currentPropValues.size()]), + currentPropMultipleStatus); + state.props.add(prop); + } + // reset temp fields + currentPropValues.clear(); + } else if (namespaceURI.equals(NamespaceConstants.NAMESPACE_SV) && "value".equals(localName)) { + // sv:value element + currentPropValues.add(currentPropValue); + // reset temp fields + currentPropValue = null; + } else { + throw new SAXException(new InvalidSerializedDataException("invalid element in system view xml document: " + localName)); + } + } + + //--------------------------------------------------------< inner classes > + + /** + * The state of parsing the XML stream. + */ + static class ImportState { + /** + * name of current node + */ + String nodeName; + /** + * primary type of current node + */ + String nodeTypeName; + /** + * list of mixin types of current node + */ + List<String> mixinNames; + /** + * uuid of current node + */ + String uuid; + + /** + * list of PropInfo instances representing properties of current node + */ + List<PropInfo> props = new ArrayList<PropInfo>(); + + /** + * flag indicating whether startNode() has been called for current node + */ + boolean started; + } + + //-------------------------------------------------------------< private > + + /** + * Returns the value of the named XML attribute. + * + * @param attributes set of XML attributes + * @param namespaceUri attribute namespace + * @param localName attribute local name + * @return attribute value, + * or <code>null</code> if the named attribute is not found + */ + private static String getAttribute(Attributes attributes, String namespaceUri, String localName) { + return attributes.getValue(namespaceUri, localName); + } + +}
Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/TargetImportHandler.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/TargetImportHandler.java?rev=1453985&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/TargetImportHandler.java (added) +++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/TargetImportHandler.java Thu Mar 7 18:18:19 2013 @@ -0,0 +1,213 @@ +/* + * 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.jackrabbit.oak.jcr.xml; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.jcr.RepositoryException; +import javax.jcr.ValueFactory; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import org.apache.jackrabbit.commons.NamespaceHelper; +import org.apache.jackrabbit.oak.namepath.LocalNameMapper; +import org.apache.jackrabbit.oak.namepath.NamePathMapper; +import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * <code>TargetImportHandler</code> serves as the base class for the concrete + * classes <code>{@link DocViewImportHandler}</code> and + * <code>{@link SysViewImportHandler}</code>. + */ +public abstract class TargetImportHandler extends DefaultHandler { + + protected final Importer importer; + protected final ValueFactory valueFactory; + protected final NamespaceHelper helper; + private final ListMultimap<String, String> documentContext = ArrayListMultimap.create(); + private Map<String, String> documentPrefixMap = Collections.emptyMap(); + + protected TargetImportHandler(Importer importer, ValueFactory valueFactory, NamespaceHelper helper) { + this.importer = importer; + this.valueFactory = valueFactory; + this.helper = helper; + } + + //--------------------------------------------------------< ImportHandler > + + @Override + public void startPrefixMapping(String prefix, String uri) throws SAXException { + documentContext.put(prefix, uri); + documentPrefixMap = createCurrentPrefixMap(); + } + + @Override + public void endPrefixMapping(String prefix) throws SAXException { + List<String> uris = documentContext.get(prefix); + if (!uris.isEmpty()) { + uris.remove(uris.size() - 1); + } + documentPrefixMap = createCurrentPrefixMap(); + } + + /** + * Initializes the underlying {@link Importer} instance. This method + * is called by the XML parser when the XML document starts. + * + * @throws SAXException if the importer can not be initialized + * @see DefaultHandler#startDocument() + */ + @Override + public void startDocument() throws SAXException { + try { + importer.start(); + } catch (RepositoryException re) { + throw new SAXException(re); + } + } + + /** + * Closes the underlying {@link Importer} instance. This method + * is called by the XML parser when the XML document ends. + * + * @throws SAXException if the importer can not be closed + * @see DefaultHandler#endDocument() + */ + @Override + public void endDocument() throws SAXException { + try { + importer.end(); + } catch (RepositoryException re) { + throw new SAXException(re); + } + } + + + //-------------------------------------------------------- + + public NamePathMapper currentNamePathMapper() { + return new NamePathMapperImpl(new LocalNameMapper(documentPrefixMap) { + @Override + protected Map<String, String> getNamespaceMap() { + try { + return helper.getNamespaces(); + } catch (RepositoryException e) { + return Collections.emptyMap(); + } + } + }); + } + + private Map<String, String> createCurrentPrefixMap() { + Map<String, String> result = new HashMap<String, String>(); + Set<Map.Entry<String, Collection<String>>> entries = documentContext.asMap().entrySet(); + for (Map.Entry<String, Collection<String>> entry : entries) { + String key = entry.getKey(); + List<String> value = (List<String>) entry.getValue(); + if (value != null && !value.isEmpty()) { + result.put(key, value.get(value.size() - 1)); + } + } + return result; + } + + + protected class NameInfo { + + private final String localName; + private final String docPrefix; + private String namespaceUri; + private String repoPrefix; + + NameInfo(String docQualifiedName) throws RepositoryException { + int idx = docQualifiedName.indexOf(":"); + if (idx == -1) { + docPrefix = null; + localName = docQualifiedName; + } else { + String[] splits = docQualifiedName.split(":", 2); + docPrefix = splits[0]; + localName = splits[1]; + } + init(); + } + + NameInfo(String docPrefix, String localName) throws RepositoryException { + this.localName = localName; + this.docPrefix = docPrefix; + init(); + } + + private void init() throws RepositoryException { + if (docPrefix == null) { + namespaceUri = ""; + repoPrefix = null; + } else { + List<String> uris = documentContext.get(docPrefix); + if (uris.isEmpty()) { + namespaceUri = helper.getURI(docPrefix); + repoPrefix = docPrefix; + } else { + namespaceUri = uris.get(uris.size() - 1); + repoPrefix = helper.getPrefix(namespaceUri); + } + } + } + + String getLocalName() { + return localName; + } + + String getNamespaceUri() { + return namespaceUri; + } + + String getDocPrefix() { + return docPrefix; + } + + String getRepoPrefix() { + return repoPrefix; + } + + String getDocQualifiedName() { + if (docPrefix == null || docPrefix.length() == 0) { + return localName; + } else { + return docPrefix + ":" + localName; + } + } + + String getRepoQualifiedName() { + if (repoPrefix == null || repoPrefix.length() == 0) { + return localName; + } else { + return repoPrefix + ":" + localName; + } + } + + String getExpandedName() { + return "{" + namespaceUri + "}" + localName; + } + } +}
