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;
+ }
+
+}