You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2008/05/14 11:44:44 UTC

svn commit: r656194 - in /jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons: AbstractSession.java xml/DocumentViewExporter.java xml/Exporter.java xml/SystemViewExporter.java

Author: jukka
Date: Wed May 14 02:44:44 2008
New Revision: 656194

URL: http://svn.apache.org/viewvc?rev=656194&view=rev
Log:
JCR-1579: Improved XML export handling
    - XML export functionality in jcr-commons

Added:
    jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/DocumentViewExporter.java
    jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/Exporter.java
    jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SystemViewExporter.java
Modified:
    jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java

Modified: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java?rev=656194&r1=656193&r2=656194&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java Wed May 14 02:44:44 2008
@@ -42,7 +42,10 @@
 import javax.xml.transform.sax.TransformerHandler;
 import javax.xml.transform.stream.StreamResult;
 
+import org.apache.jackrabbit.commons.xml.DocumentViewExporter;
+import org.apache.jackrabbit.commons.xml.Exporter;
 import org.apache.jackrabbit.commons.xml.ParsingContentHandler;
+import org.apache.jackrabbit.commons.xml.SystemViewExporter;
 import org.apache.jackrabbit.util.XMLChar;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
@@ -206,6 +209,46 @@
     //---------------------------------------------< XML export and import >--
 
     /**
+     * Generates a document view export using a {@link DocumentViewExporter}
+     * instance.
+     *
+     * @param path of the node to be exported
+     * @param handler handler for the SAX events of the export
+     * @param skipBinary whether binary values should be skipped
+     * @param noRecurse whether to export just the identified node
+     * @throws PathNotFoundException if a node at the given path does not exist
+     * @throws SAXException if the SAX event handler failed
+     * @throws RepositoryException if another error occurs
+     */
+    public void exportDocumentView(
+            String path, ContentHandler handler,
+            boolean skipBinary, boolean noRecurse)
+            throws PathNotFoundException, SAXException, RepositoryException {
+        export(path, new DocumentViewExporter(
+                this, handler, !noRecurse, !skipBinary));
+    }
+
+    /**
+     * Generates a system view export using a {@link SystemViewExporter}
+     * instance.
+     *
+     * @param path of the node to be exported
+     * @param handler handler for the SAX events of the export
+     * @param skipBinary whether binary values should be skipped
+     * @param noRecurse whether to export just the identified node
+     * @throws PathNotFoundException if a node at the given path does not exist
+     * @throws SAXException if the SAX event handler failed
+     * @throws RepositoryException if another error occurs
+     */
+    public void exportSystemView(
+            String path, ContentHandler handler,
+            boolean skipBinary, boolean noRecurse)
+            throws PathNotFoundException, SAXException, RepositoryException {
+        export(path, new SystemViewExporter(
+                this, handler, !noRecurse, !skipBinary));
+    }
+
+    /**
      * Calls {@link Session#exportDocumentView(String, ContentHandler, boolean, boolean)}
      * with the given arguments and a {@link ContentHandler} that serializes
      * SAX events to the given output stream.
@@ -390,6 +433,25 @@
     //-------------------------------------------------------------< private >
 
     /**
+     * Exports content at the given path using the given exporter.
+     *
+     * @param path of the node to be exported
+     * @param exporter document or system view exporter
+     * @throws SAXException if the SAX event handler failed
+     * @throws RepositoryException if another error occurs
+     */
+    private void export(String path, Exporter exporter)
+            throws PathNotFoundException, SAXException, RepositoryException {
+        Item item = getItem(path);
+        if (item.isNode()) {
+            exporter.export((Node) item);
+        } else {
+            throw new PathNotFoundException(
+                    "XML export is not defined for properties: " + path);
+        }
+    }
+
+    /**
      * Creates a {@link ContentHandler} instance that serializes the
      * received SAX events to the given output stream.
      *

Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/DocumentViewExporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/DocumentViewExporter.java?rev=656194&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/DocumentViewExporter.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/DocumentViewExporter.java Wed May 14 02:44:44 2008
@@ -0,0 +1,105 @@
+/*
+ * 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.commons.xml;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.util.ISO9075;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * Document view exporter.
+ *
+ * @since Jackrabbit JCR Commons 1.5
+ */
+public class DocumentViewExporter extends Exporter {
+
+    /**
+     * Creates a document view exporter.
+     *
+     * @param session current session
+     * @param handler SAX event handler for the export
+     * @param recurse whether to recursively export the whole subtree
+     * @param binary whether to export binary values
+     */
+    public DocumentViewExporter(
+            Session session, ContentHandler handler,
+            boolean recurse, boolean binary) {
+        super(session, handler, recurse, binary);
+    }
+
+    /**
+     * Exports the given node either as XML characters (if it's an
+     * <code>xml:text</code> node) or as an XML element with properties
+     * mapped to XML attributes.
+     */
+    protected void exportNode(String uri, String local, Node node)
+            throws RepositoryException, SAXException {
+        if (JCR.equals(uri) && "xmltext".equals(local)) {
+            try {
+                // assume jcr:xmlcharacters is single-valued
+                Property property =
+                    node.getProperty(getJCRName(JCR, "xmlcharacters"));
+                char[] ch = property.getString().toCharArray();
+                characters(ch, 0, ch.length);
+            } catch (PathNotFoundException e) {
+                // jcr:xmlcharacters not found, ignore this node
+            }
+        } else {
+            // attributes (properties)
+            exportProperties(node);
+
+            // encode node name to make sure it's a valid xml name
+            String encoded = ISO9075.encode(local);
+            startElement(uri, encoded);
+            exportNodes(node);
+            endElement(uri, encoded);
+        }
+    }
+
+    /**
+     * Maps the given single-valued property to an XML attribute.
+     */
+    protected void exportProperty(String uri, String local, Value value)
+            throws RepositoryException {
+        // TODO: Serialized names and paths should use XML namespace mappings
+        String attribute = ValueHelper.serialize(value, false);
+        addAttribute(uri, ISO9075.encode(local), attribute);
+    }
+
+    /**
+     * Does nothing. Multi-valued properties are skipped for the time being
+     * until a way of properly handling/detecting multi-valued properties
+     * on re-import is found. Skipping multi-valued properties entirely is
+     * legal according to "6.4.2.5 Multi-value Properties" of the JSR 170
+     * specification.
+     *
+     * @see https://issues.apache.org/jira/browse/JCR-325
+     */
+    protected void exportProperty(
+            String uri, String local, int type, Value[] values) {
+        // TODO: proper multi-value serialization support
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/Exporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/Exporter.java?rev=656194&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/Exporter.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/Exporter.java Wed May 14 02:44:44 2008
@@ -0,0 +1,581 @@
+/*
+ * 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.commons.xml;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.NamespaceSupport;
+
+/**
+ * Abstract base class for document and system view exporters. This class
+ * takes care of all the details related to namespace mappings, shareable
+ * nodes, recursive exports, binary values, and so on while leaving the
+ * decisions about what kind of SAX events to generate to subclasses.
+ * <p>
+ * A subclass should only need to implement the abstract methods of this
+ * class to produce a fully functional exporter.
+ *
+ * @since Jackrabbit JCR Commons 1.5
+ */
+public abstract class Exporter {
+
+    /**
+     * The <code>jcr</code> namespace URI.
+     */
+    protected static final String JCR = "http://www.jcp.org/jcr/1.0";
+
+    /**
+     * The <code>nt</code> namespace URI.
+     */
+    private static final String NT = "http://www.jcp.org/jcr/nt/1.0";
+
+    /**
+     * The <code>mix</code> namespace URI.
+     */
+    private static final String MIX = "http://www.jcp.org/jcr/mix/1.0";
+
+    /**
+     * Attributes of the next element. This single instance is reused for
+     * all elements by simply clearing it after each element has been emitted.
+     */
+    private final AttributesImpl attributes = new AttributesImpl();
+
+    /**
+     * Stack of namespace mappings. 
+     */
+    private final LinkedList stack = new LinkedList();
+
+    /**
+     * The UUID strings of all shareable nodes already exported.
+     */
+    private final Set shareables = new HashSet();
+
+    /**
+     * Whether the current node is a shareable node that has already been
+     * exported.
+     */
+    private boolean share = false;
+
+    /**
+     * Current session.
+     */
+    private final Session session;
+
+    /**
+     * SAX event handler to which the export events are sent.
+     */
+    private final ContentHandler handler;
+
+    /**
+     * Whether to export the subtree or just the given node.
+     */
+    private final boolean recurse;
+
+    /**
+     * Whether to export binary values.
+     */
+    private final boolean binary;
+
+    /**
+     * Creates an exporter instance.
+     *
+     * @param session current session
+     * @param handler SAX event handler
+     * @param recurse whether the export should be recursive
+     * @param binary whether the export should include binary values
+     */
+    protected Exporter(
+            Session session, ContentHandler handler,
+            boolean recurse, boolean binary) {
+        this.session = session;
+        this.handler = handler;
+        this.recurse = recurse;
+        this.binary = binary;
+        stack.add(new HashMap());
+    }
+
+    /**
+     * Exports the given node by preparing the export and calling the
+     * abstract {@link #exportNode(String, String, Node)} method to give
+     * control of the export format to a subclass.
+     * <p>
+     * This method should be called only once for an exporter instance.
+     *
+     * @param node node to be exported
+     * @throws SAXException if a SAX error occurs
+     * @throws RepositoryException if a repository error occurs
+     */
+    public void export(Node node) throws RepositoryException, SAXException {
+        handler.startDocument();
+
+        String[] prefixes = session.getNamespacePrefixes();
+        for (int i = 0; i < prefixes.length; i++) {
+            if (prefixes[i].length() > 0 && !prefixes[i].equals("xml") ) {
+                addNamespace(prefixes[i], session.getNamespaceURI(prefixes[i]));
+            }
+        }
+
+        exportNode(node);
+
+        handler.endDocument();
+    }
+
+    /**
+     * Called to export the given node. The node name (or <code>jcr:root</code>
+     * if the node is the root node) is given as an explicit pair of the
+     * resolved namespace URI and local part of the name.
+     * <p>
+     * The implementation of this method should call the methods
+     * {@link #exportProperties(Node)} and {@link #exportProperties(Node)}
+     * to respectively export the properties and child nodes of the given node.
+     * Those methods will call back to the implementations of this method and
+     * the abstract property export methods so the subclass can decide what
+     * SAX events to emit for each exported item.
+     *
+     * @param uri node namespace, or <code>null</code>
+     * @param local node name
+     * @param node node
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected abstract void exportNode(String uri, String local, Node node)
+            throws RepositoryException, SAXException;
+
+    /**
+     * Called by {@link #processProperties(Node)} to process a single-valued
+     * property.
+     *
+     * @param uri property namespace, or <code>null</code>
+     * @param local property name
+     * @param value property value
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected abstract void exportProperty(
+            String uri, String local, Value value)
+            throws RepositoryException, SAXException;
+
+    /**
+     * Called by {@link #processProperties(Node)} to process a multivalued
+     * property.
+     *
+     * @param uri property namespace, or <code>null</code>
+     * @param local property name
+     * @param type property type
+     * @param value property values
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected abstract void exportProperty(
+            String uri, String local, int type, Value[] values)
+            throws RepositoryException, SAXException;
+
+    /**
+     * Called by {@link #exportNode(String, String, Node)} to recursively
+     * call {@link #exportNode(String, String, Node)} for each child node.
+     * Does nothing if this exporter is not recursive. 
+     *
+     * @param node parent node
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected void exportNodes(Node node)
+            throws RepositoryException, SAXException {
+        if (recurse && !share) {
+            NodeIterator iterator = node.getNodes();
+            while (iterator.hasNext()) {
+                Node child = iterator.nextNode();
+                exportNode(child);
+            }
+        }
+    }
+
+    /**
+     * Processes all properties of the given node by calling the abstract
+     * {@link #exportProperty(String, String, Value)} and
+     * {@link #exportProperty(String, String, int, Value[])} methods for
+     * each property depending on whether the property is single- or
+     * multivalued.
+     * <p>
+     * The first properties to be processed are <code>jcr:primaryType</code>,
+     * <code>jcr:mixinTypes</code>, and <code>jcr:uuid</code>, and then the
+     * remaining properties ordered by their names.
+     * <p>
+     * If the node is a shareable node that has already been encountered by
+     * this event generator, then only a <code>jcr:primaryType</code> property
+     * with the fixed value "nt:share" and the <code>jcr:uuid</code> property
+     * of the shareable node are exported.
+     *
+     * @see https://issues.apache.org/jira/browse/JCR-1084
+     * @param node node
+     * @return properties as sorted map
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected void exportProperties(Node node)
+            throws RepositoryException, SAXException {
+        // if node is shareable and has already been serialized, change its
+        // type to nt:share and process only the properties jcr:primaryType
+        // and jcr:uuid (mix:shareable is referenceable, so jcr:uuid exists)
+        if (share) {
+            ValueFactory factory = session.getValueFactory();
+            Value share =
+                factory.createValue(getJCRName(NT, "share"), PropertyType.NAME);
+            exportProperty(JCR, "primaryType", share);
+            exportProperty(JCR, "uuid", factory.createValue(node.getUUID()));
+        } else {
+            // Standard behaviour: return all properties (sorted, see JCR-1084)
+            SortedMap properties = getProperties(node);
+
+            // serialize jcr:primaryType, jcr:mixinTypes & jcr:uuid first:
+            exportProperty(properties, JCR, "primaryType");
+            exportProperty(properties, JCR, "mixinTypes");
+            exportProperty(properties, JCR, "uuid");
+
+            // serialize remaining properties
+            Iterator iterator = properties.entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                String uri = null;
+                String name = (String) entry.getKey();
+                int colon = name.indexOf(':');
+                if (colon != -1) {
+                    uri = session.getNamespaceURI(name.substring(0, colon));
+                    name = name.substring(colon + 1);
+                }
+                exportProperty(uri, name, (Property) entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * Utility method for exporting the given node. Parses the node name
+     * (or <code>jcr:root</code> if given the root node) and calls
+     * {@link #exportNode(String, String, Node)} with the resolved namespace
+     * URI and the local part of the name.
+     *
+     * @param node node
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    private void exportNode(Node node)
+            throws RepositoryException, SAXException {
+        share = node.isNodeType(getJCRName(MIX, "shareable"))
+            && !shareables.add(node.getUUID());
+
+        if (node.getDepth() == 0) {
+            exportNode(JCR, "root", node);
+        } else {
+            String name = node.getName();
+            int colon = name.indexOf(':');
+            if (colon == -1) {
+                exportNode(null, name, node);
+            } else {
+                String uri = session.getNamespaceURI(name.substring(0, colon));
+                exportNode(uri, name.substring(colon + 1), node);
+            }
+        }
+    }
+
+    /**
+     * Returns a sorted map of the properties of the given node.
+     *
+     * @param node JCR node
+     * @return sorted map (keyed by name) of properties
+     * @throws RepositoryException if a repository error occurs
+     */
+    private SortedMap getProperties(Node node) throws RepositoryException {
+        SortedMap properties = new TreeMap();
+        PropertyIterator iterator = node.getProperties();
+        while (iterator.hasNext()) {
+            Property property = iterator.nextProperty();
+            properties.put(property.getName(), property);
+        }
+        return properties;
+    }
+
+    /**
+     * Utility method for processing the named property from the given
+     * map of properties. If the property exists, it is removed from the
+     * given map and passed to {@link #exportProperty(Property)}.
+     * The property is ignored if it does not exist.
+     *
+     * @param properties map of properties
+     * @param uri property namespace
+     * @param local property name
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    private void exportProperty(Map properties, String uri, String local)
+            throws RepositoryException, SAXException {
+        Property property = (Property) properties.remove(getJCRName(uri, local));
+        if (property != null) {
+            exportProperty(uri, local, property);
+        }
+    }
+
+    /**
+     * Utility method for processing the given property. Calls either
+     * {@link #exportProperty(Value)} or {@link #exportProperty(int, Value[])}
+     * depending on whether the the property is single- or multivalued.
+     *
+     * @param uri property namespace
+     * @param local property name
+     * @param property property
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    private void exportProperty(String uri, String local, Property property)
+            throws RepositoryException, SAXException {
+        int type = property.getType();
+        if (type != PropertyType.BINARY || binary) {
+            if (property.getDefinition().isMultiple()) {
+                exportProperty(uri, local, type, property.getValues());
+            } else {
+                exportProperty(uri, local, property.getValue());
+            }
+        } else {
+            ValueFactory factory = session.getValueFactory();
+            Value value = factory.createValue("", PropertyType.BINARY);
+            if (property.getDefinition().isMultiple()) {
+                exportProperty(uri, local, type, new Value[] { value });
+            } else {
+                exportProperty(uri, local, value);
+            }
+        }
+    }
+
+    //---------------------------------------------< XML handling methods >--
+
+    /**
+     * Emits a characters event with the given character content.
+     *
+     * @param ch character array
+     * @param start start offset within the array
+     * @param length number of characters to emit
+     * @throws SAXException if a SAX error occurs
+     */
+    protected void characters(char[] ch, int start, int length)
+            throws SAXException {
+        handler.characters(ch, start, length);
+    }
+
+    /**
+     * Adds the given attribute to be included in the next element.
+     *
+     * @param uri namespace URI of the attribute
+     * @param local local name of the attribute
+     * @param value attribute value
+     * @throws RepositoryException if a repository error occurs
+     */
+    protected void addAttribute(String uri, String local, String value)
+            throws RepositoryException {
+        attributes.addAttribute(
+                uri, local, getXMLName(uri, local), "CDATA", value);
+    }
+
+    /**
+     * Emits the start element event for an element with the given name.
+     * All the attributes added using
+     * {@link #addAttribute(String, String, String)} are included in the
+     * element along with any new namespace mappings. The namespace stack
+     * is extended for potential child elements.
+     *
+     * @param uri namespace URI or the element
+     * @param local local name of the element
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected void startElement(String uri, String local)
+            throws SAXException, RepositoryException {
+        // Prefixed name is generated before namespace handling so that a
+        // potential new prefix mapping gets included as a xmlns attribute
+        String name = getXMLName(uri, local);
+
+        // Add namespace mappings
+        Map namespaces = (Map) stack.getFirst();
+        Iterator iterator = namespaces.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry entry = (Map.Entry) iterator.next();
+            String namespace = (String) entry.getKey();
+            String prefix = (String) entry.getValue();
+            handler.startPrefixMapping(prefix, namespace);
+            attributes.addAttribute(
+                    NamespaceSupport.XMLNS, prefix, "xmlns:" + prefix,
+                    "CDATA", namespace);
+        }
+
+        // Emit the start element event, and clear things for the next element
+        handler.startElement(uri, local, name, attributes);
+        attributes.clear();
+        stack.addFirst(new HashMap());
+    }
+
+    /**
+     * Emits the end element event for an element with the given name.
+     * The namespace stack and mappings are automatically updated.
+     *
+     * @param uri namespace URI or the element
+     * @param local local name of the element
+     * @throws RepositoryException if a repository error occurs
+     * @throws SAXException if a SAX error occurs
+     */
+    protected void endElement(String uri, String local)
+            throws SAXException, RepositoryException {
+        stack.removeFirst();
+        handler.endElement(uri, local, getXMLName(uri, local));
+
+        Map namespaces = (Map) stack.getFirst();
+        Iterator iterator = namespaces.values().iterator();
+        while (iterator.hasNext()) {
+            handler.endPrefixMapping((String) iterator.next());
+        }
+        namespaces.clear();
+    }
+
+    /**
+     * Returns the prefixed JCR name for the given namespace URI and local
+     * name.
+     *
+     * @param uri namespace URI (must not be the empty namespace)
+     * @param name local name
+     * @return prefixed JCR name
+     * @throws RepositoryException if a JCR namespace mapping is not available
+     */
+    protected String getJCRName(String uri, String name)
+            throws RepositoryException {
+        return session.getNamespacePrefix(uri) + ":" + name;
+    }
+
+    /**
+     * Returns a prefixed XML name for the given namespace URI and local
+     * name. If a prefix mapping for the namespace URI is not yet available,
+     * it is created based on the namespace mappings of the current JCR
+     * session.
+     *
+     * @param uri namespace URI, or <code>null</code>
+     * @param local local name
+     * @return prefixed XML name
+     * @throws RepositoryException if a JCR namespace mapping is not available
+     */
+    protected String getXMLName(String uri, String local)
+            throws RepositoryException {
+        if (uri == null) {
+            return local;
+        } else {
+            String prefix = getPrefix(uri);
+            if (prefix == null) {
+                prefix = getUniquePrefix(session.getNamespacePrefix(uri));
+                ((Map) stack.getFirst()).put(uri, prefix);
+            }
+            return prefix + ":" + local;
+        }
+    }
+
+    /**
+     * Adds the given namespace to the export. A unique prefix based on
+     * the given prefix hint is mapped to the given namespace URI. If the
+     * namespace is already mapped, then the existing prefix is returned.
+     *
+     * @param hint prefix hint
+     * @param uri namespace URI
+     * @return registered prefix
+     */
+    protected String addNamespace(String hint, String uri) {
+        String prefix = getPrefix(uri);
+        if (prefix == null) {
+            prefix = getUniquePrefix(hint);
+            ((Map) stack.getFirst()).put(uri, prefix);
+        }
+        return prefix;
+    }
+
+    /**
+     * Returns the namespace prefix mapped to the given URI. Returns
+     * <code>null</code> if the namespace URI is not registered.
+     *
+     * @param uri namespace URI
+     * @return prefix mapped to the URI, or <code>null</code>
+     */
+    private String getPrefix(String uri) {
+        Iterator iterator = stack.iterator();
+        while (iterator.hasNext()) {
+            String prefix = (String) ((Map) iterator.next()).get(uri);
+            if (prefix != null) {
+                return prefix;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a unique namespace prefix based on the given hint.
+     * We need prefixes to be unique within the current namespace
+     * stack as otherwise for example a previously added attribute
+     * to the current element might incorrectly be using a prefix
+     * that is being redefined in this element.
+     *
+     * @param hint prefix hint
+     * @return unique prefix
+     */
+    private String getUniquePrefix(String hint) {
+        String prefix = hint;
+        for (int i = 2; prefixExists(prefix); i++) {
+            prefix = hint + i;
+        }
+        return prefix;
+    }
+
+    /**
+     * Checks whether the given prefix is already mapped within the
+     * current namespace stack.
+     *
+     * @param prefix namespace prefix
+     * @return <code>true</code> if the prefix is mapped,
+     *         <code>false</code> otherwise
+     */
+    private boolean prefixExists(String prefix) {
+        Iterator iterator = stack.iterator();
+        while (iterator.hasNext()) {
+            if (((Map) iterator.next()).containsValue(prefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SystemViewExporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SystemViewExporter.java?rev=656194&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SystemViewExporter.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SystemViewExporter.java Wed May 14 02:44:44 2008
@@ -0,0 +1,171 @@
+/*
+ * 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.commons.xml;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.value.ValueHelper;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * System view exporter.
+ *
+ * @since Jackrabbit JCR Commons 1.5
+ */
+public class SystemViewExporter extends Exporter {
+
+    /**
+     * The <code>sv</code> namespace URI.
+     */
+    private static final String SV = "http://www.jcp.org/jcr/sv/1.0";
+
+    /**
+     * The <code>xs</code> namespace URI.
+     */
+    private static final String XS = "http://www.w3.org/2001/XMLSchema";
+
+    /**
+     * The <code>xsi</code> namespace URI.
+     */
+    private static final String XSI =
+        "http://www.w3.org/2001/XMLSchema-instance";
+
+    /**
+     * Creates a system view exporter.
+     *
+     * @param session current session
+     * @param handler SAX event handler for the export
+     * @param recurse whether to recursively export the whole subtree
+     * @param binary whether to export binary values
+     */
+    public SystemViewExporter(
+            Session session, ContentHandler handler,
+            boolean recurse, boolean binary) {
+        super(session, handler, recurse, binary);
+    }
+
+    /**
+     * Exports the given node as an <code>sv:node</code> element.
+     */
+    protected void exportNode(String uri, String local, Node node)
+            throws RepositoryException, SAXException {
+        addAttribute(SV, "name", getXMLName(uri, local));
+        startElement(SV, "node");
+        exportProperties(node);
+        exportNodes(node);
+        endElement(SV, "node");
+    }
+
+    /**
+     * Calls {@link #exportProperty(String, String, int, Value[])}.
+     */
+    protected void exportProperty(String uri, String local, Value value)
+            throws RepositoryException, SAXException {
+        exportProperty(uri, local, value.getType(), new Value[] { value });
+    }
+
+    /**
+     * Exports the given property as an <code>sv:property</code> element.
+     */
+    protected void exportProperty(
+            String uri, String local, int type, Value[] values)
+            throws RepositoryException, SAXException {
+        // start property element
+        addAttribute(SV, "name", getXMLName(uri, local));
+        addAttribute(SV, "type", PropertyType.nameFromValue(type));
+        startElement(SV, "property");
+        // values
+        for (int i = 0; i < values.length; i++) {
+            exportValue(values[i]);
+        }
+        endElement(SV, "property");
+    }
+
+    /**
+     * Exports the given value as an <code>sv:value</code> element.
+     *
+     * @param value value to be exported
+     */
+    private void exportValue(Value value)
+            throws RepositoryException, SAXException {
+        try {
+            boolean binary = mustSendBinary(value);
+            if (binary) {
+                addNamespace("xs", XS);
+                addNamespace("xsi", XSI);
+                addAttribute(XSI, "type", getXMLName(XS, "base64Binary"));
+            }
+            startElement(SV, "value");
+            ValueHelper.serialize(value, false, binary, new Writer() {
+                public void write(char[] cbuf, int off, int len)
+                        throws IOException {
+                    try {
+                        SystemViewExporter.this.characters(cbuf, off, len);
+                    } catch (Exception e) {
+                        IOException exception = new IOException();
+                        exception.initCause(e);
+                        throw exception;
+                    }
+                }
+                public void close() {
+                }
+                public void flush() {
+                }
+            });
+            endElement(SV, "value");
+        } catch (IOException e) {
+            // check if the exception wraps a SAXException
+            // (see Writer.write(char[], int, int) above)
+            if (e.getCause() instanceof SAXException) {
+                throw (SAXException) e.getCause();
+            } else {
+                throw new RepositoryException(e);
+            }
+        }
+    }
+
+    /**
+     * Utility method for determining whether a non-binary value should
+     * still be exported in base64 encoding to avoid emitting invalid XML
+     * characters.
+     *
+     * @param value value to be exported
+     * @return whether the value should be treated as a binary
+     * @throws RepositoryException if a repository error occurs
+     */
+    private boolean mustSendBinary(Value value) throws RepositoryException {
+        if (value.getType() != PropertyType.BINARY) {
+            String string = value.getString();
+            for (int i = 0; i < string.length(); i++) {
+                char c = string.charAt(i);
+                if (c >= 0 && c < 32 && c != '\r' && c != '\n' && c != '\t') {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}