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 2005/04/16 07:46:26 UTC

svn commit: r161553 [4/4] - in incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org: ./ apache/ apache/jackrabbit/ apache/jackrabbit/base/ apache/jackrabbit/base/nodetype/ apache/jackrabbit/decorator/ apache/jackrabbit/iterator/ apache/jackrabbit/lite/ apache/jackrabbit/lite/nodetype/ apache/jackrabbit/name/ apache/jackrabbit/trace/ apache/jackrabbit/xml/

Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,502 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Item;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+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.ValueFormatException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.jackrabbit.name.Name;
+import org.apache.xerces.util.XMLChar;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Generic document view exporter for JCR content repositories.
+ * This class can be used to implement the XML document view export
+ * operations using nothing but the standard JCR interfaces. The
+ * export operation is implemented as an ItemVisitor that generates
+ * the document view SAX event stream as it traverses the selected
+ * JCR content tree.
+ *
+ * <h2>Implementing a customized XML serializer</h2>
+ * <p>
+ * A client can extend this class to provide customized XML serialization
+ * formats. By overriding the protected includeProperty() and includeNode()
+ * methods, a subclass can select which properties and nodes will be included
+ * in the serialized XML stream.
+ * <p>
+ * For example, the following code implements an XML serialization that only
+ * contains the titles of the first two levels of the node tree.
+ * <pre>
+ *     ContentHandler handler = ...;
+ *     Node parent = ...;
+ *     parent.accept(
+ *         new DocumentViewExportVisitor(handler, true, false) {
+ *
+ *             protected boolean includeProperty(Property property)
+ *                     throws RepositoryException {
+ *                 if (property.getName().equals("title")) {
+ *                     return super.includeProperty(property);
+ *                 } else {
+ *                     return false;
+ *                 }
+ *             }
+ *
+ *             protected boolean includeNode(Node node)
+ *                     throws RepositoryException {
+ *                 return (node.getDepth() <= root.getDepth() + 2);
+ *             }
+ *
+ *         });
+ * </pre>
+ *
+ * <h2>Implementing the standard export methods</h2>
+ * <p>
+ * The following is an example of the
+ * Session.exportDocView(String, ContentHandler, boolean, boolean)
+ * method implemented in terms of this exporter class:
+ * <pre>
+ *     public void exportDocView(String absPath, ContentHandler handler,
+ *             boolean skipBinary, boolean noRecurse) throws
+ *             InvalidSerializedDataException, PathNotFoundException,
+ *             SAXException, RepositoryException {
+ *         Item item = getItem(absPath);
+ *         if (item.isNode()) {
+ *             item.accept(new DocumentViewExportVisitor(
+ *                     handler, skipBinary, noRecurse));
+ *         } else {
+ *             throw new PathNotFoundException("Invalid node path: " + path);
+ *         }
+ *     }
+ * </pre>
+ * <p>
+ * The companion method
+ * Session.exportDocView(String, OutputStream, boolean, boolean)
+ * can be implemented in terms of the above method and the XMLSerializer
+ * class from the Xerces library:
+ * <pre>
+ * import org.apache.xml.serialize.XMLSerializer;
+ * import org.apache.xml.serialize.OutputFormat;
+ *
+ *     public void exportDocView(String absPath, OutputStream output,
+ *             boolean skipBinary, boolean noRecurse) throws
+ *             InvalidSerializedDataException, PathNotFoundException,
+ *             IOException, RepositoryException {
+ *         try {
+ *             XMLSerializer serializer =
+ *                 new XMLSerializer(output, new OutputFormat());
+ *             exportDocView(absPath, serializer.asContentHandler(),
+ *                     binaryAsLink, noRecurse);
+ *         } catch (SAXException ex) {
+ *             throw new IOException(ex.getMessage());
+ *         }
+ *     }
+ * </pre>
+ *
+ * @author Jukka Zitting
+ * @see ItemVisitor
+ * @see Session#exportDocView(String, ContentHandler, boolean, boolean)
+ * @see Session#exportDocView(String, java.io.OutputStream, boolean, boolean)
+ */
+public class DocumentViewExportVisitor implements ItemVisitor {
+
+    /** The special jcr:xmltext property name. */
+    private static final String XMLTEXT = "jcr:xmltext";
+
+    /** The special jcr:xmlcharacters property name. */
+    private static final String XMLCHARACTERS = "jcr:xmlcharacters";
+
+    /**
+     * The SAX content handler for the serialized XML stream.
+     */
+    private ContentHandler handler;
+
+    /**
+     * Flag to skip all binary properties.
+     */
+    private boolean skipBinary;
+
+    /**
+     * Flag to only serialize the selected node.
+     */
+    private boolean noRecurse;
+
+    /**
+     * The root node of the serialization tree. This is the node that
+     * is mapped to the root element of the serialized XML stream.
+     */
+    protected Node root;
+
+    /**
+     * Creates an visitor for exporting content using the document view
+     * format. To actually perform the export operation, you need to pass
+     * the visitor instance to the selected content node using the
+     * Node.accept(ItemVisitor) method.
+     *
+     * @param handler the SAX event handler
+     * @param skipBinary flag for ignoring binary properties
+     * @param noRecurse flag for not exporting an entire content subtree
+     */
+    public DocumentViewExportVisitor(
+            ContentHandler handler, boolean skipBinary, boolean noRecurse) {
+        this.handler = handler;
+        this.skipBinary = skipBinary;
+        this.noRecurse = noRecurse;
+        this.root = null;
+    }
+
+    /**
+     * Ignored. Properties are included as attributes of node elements.
+     * {@inheritDoc}
+     */
+    public void visit(Property property) throws RepositoryException {
+    }
+
+    /**
+     * Exports the visited node using the document view serialization format.
+     * This method is the main entry point to the serialization mechanism.
+     * It manages the opening and closing of the SAX event stream and the
+     * registration of the namespace mappings. The process of actually
+     * generating the document view SAX events is spread into various
+     * private methods, and can be controlled by overriding the protected
+     * includeProperty() and includeNode() methods.
+     *
+     * @param node the node to visit
+     * @throws RepositoryException on repository errors
+     */
+    public void visit(Node node) throws RepositoryException {
+        try {
+            // start document
+            if (root == null) {
+                root = node;
+                handler.startDocument();
+
+                Session session = root.getSession();
+                String[] prefixes = session.getNamespacePrefixes();
+                for (int i = 0; i < prefixes.length; i++) {
+                    handler.startPrefixMapping(prefixes[i],
+                            session.getNamespaceURI(prefixes[i]));
+                }
+            }
+
+            // export current node
+            if (!node.getName().equals(XMLTEXT)) {
+                exportNode(node);
+            } else if (node != root) {
+                exportText(node);
+            } else {
+                throw new RepositoryException("Cannot export jcr:xmltext");
+            }
+
+            // end document
+            if (root == node) {
+                handler.endDocument();
+            }
+        } catch (SAXException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+
+    /**
+     * Checks whether the given property should be included in the XML
+     * serialization. By default this method returns false for the special
+     * "jcr:xmlcharacters" properties and for binary properties if the
+     * skipBinary flag is set. Subclasses can extend this behaviour to
+     * implement more selective XML serialization.
+     * <p>
+     * To avoid losing the default behaviour described above, subclasses
+     * should always call super.includeProperty(property) instead of
+     * simply returning true for a property.
+     *
+     * @param property the property to check
+     * @return true if the property should be included, false otherwise
+     * @throws RepositoryException on repository errors
+     */
+    protected boolean includeProperty(Property property)
+            throws RepositoryException {
+        return !property.getName().equals(XMLCHARACTERS)
+            && (!skipBinary || property.getType() != PropertyType.BINARY);
+    }
+
+    /**
+     * Checks whether the given node should be included in the XML
+     * serialization. This method returns true by default, but subclasses
+     * can extend this behaviour to implement selective XML serialization.
+     * <p>
+     * Note that this method is only called for the descendants of the
+     * root node of the serialized tree. Also, this method is never called
+     * if the noRecurse flag is set because no descendant nodes will be
+     * serialized anyway.
+     *
+     * @param node the node to check
+     * @return true if the node should be included, false otherwise
+     * @throws RepositoryException on repository errors
+     */
+    protected boolean includeNode(Node node) throws RepositoryException {
+        return true;
+    }
+
+    /**
+     * Serializes a special "jcr:xmltext" node. Only the contents of the
+     * "jcr:xmlcharacters" property will be written as characters to the
+     * XML stream and no elements or attributes will be generated for
+     * this node or any other child nodes or properties.
+     *
+     * @param node the "jcr:xmltext" node
+     * @throws SAXException on SAX errors
+     * @throws RepositoryException on repository errors
+     */
+    private void exportText(Node node)
+            throws SAXException, RepositoryException {
+        try {
+            Property property = node.getProperty(XMLCHARACTERS);
+            char[] characters = property.getString().toCharArray();
+            handler.characters(characters, 0, characters.length);
+        } catch (PathNotFoundException ex) {
+            // ignore empty jcr:xmltext nodes
+        } catch (ValueFormatException ex) {
+            // ignore non-string jcr:xmlcharacters properties
+        }
+    }
+
+    /**
+     * Serializes the given node to the XML stream. Generates an element
+     * with the same name as this node, and maps node properties to
+     * attributes of the generated element. If the noRecurse flag is false,
+     * then child nodes are serialized as sub-elements.
+     *
+     * @param node the given node
+     * @throws SAXException on SAX errors
+     * @throws RepositoryException on repository errors
+     */
+    private void exportNode(Node node)
+            throws SAXException, RepositoryException {
+        Name qname = getQName(node);
+        String localName = escapeName(qname.getLocalPart());
+        String prefixedName =
+            node.getSession().getNamespacePrefix(qname.getNamespaceURI())
+            + ":" + localName;
+
+        // Start element
+        handler.startElement(
+                qname.getNamespaceURI(), localName, prefixedName,
+                getAttributes(node));
+
+        // Visit child nodes (unless denied by the noRecurse flag)
+        if (!noRecurse) {
+            NodeIterator children = node.getNodes();
+            while (children.hasNext()) {
+                Node child = children.nextNode();
+                if (includeNode(child)) {
+                    child.accept(this);
+                }
+            }
+        }
+
+        // End element
+        handler.endElement(
+                qname.getNamespaceURI(), qname.getLocalPart(), node.getName());
+    }
+
+    /**
+     * Returns the document view attributes of the given Node. The
+     * properties of the node are mapped to XML attributes directly as
+     * name-value pairs.
+     *
+     * @param node the given node
+     * @return document view attributes of the node
+     * @throws RepositoryException on repository errors
+     */
+    private Attributes getAttributes(Node node) throws RepositoryException {
+        AttributesImpl attributes = new AttributesImpl();
+
+        PropertyIterator properties = node.getProperties();
+        while (properties.hasNext()) {
+            Property property = properties.nextProperty();
+            if (includeProperty(property)) {
+                Name qname = getQName(property);
+                String value = getValue(property);
+                attributes.addAttribute(qname.getNamespaceURI(),
+                        qname.getLocalPart(), property.getName(),
+                        "CDATA", value);
+            }
+        }
+
+        return attributes;
+    }
+
+    /**
+     * Returns the qualified XML name of the given item. If the item name
+     * is prefixed, then the respective namespace URI is looked up from the
+     * session and returned as a part of the qualified name. If the item
+     * name is not prefixed, then the returned qualified name will use the
+     * null namespace and the default prefix. The local part of the qualified
+     * name will be escaped using ISO 9075 rules. If the given item is the
+     * root node, then the special name "jcr:root" is returned
+     * <p>
+     * See section 6.4.2 of the JCR specification for more details about
+     * name handling in the XML document view serialization.
+     *
+     * @param item the given item
+     * @return qualified XML name of the item
+     * @throws RepositoryException on repository errors
+     * @see Name
+     */
+    private Name getQName(Item item) throws RepositoryException {
+        String name = item.getName();
+        if (name.length() == 0) {
+            name = "jcr:root";
+        }
+        return Name.parseJCRName(item.getSession(), name);
+    }
+
+    /**
+     * Escapes the given name or prefix according to the rules of section
+     * 6.4.3 of the JSR 170 specification (version 0.16.2).
+     *
+     * @param name original name or prefix
+     * @return escaped name or prefix
+     */
+    private String escapeName(String name) {
+        if (name.length() == 0) {
+            return name;
+        }
+
+        StringBuffer buffer = new StringBuffer();
+
+        // First character
+        if (!XMLChar.isNameStart(name.charAt(0)) || name.startsWith("_x")
+                || (name.length() >= 3
+                        && "xml".equalsIgnoreCase(name.substring(0, 3)))) {
+            String hex =
+                "000" + Integer.toHexString(name.charAt(0)).toUpperCase();
+            buffer.append("_x" + hex.substring(hex.length() - 4) + "_");
+        } else {
+            buffer.append(name.charAt(0));
+        }
+
+        // Rest of the characters
+        for (int i = 1; i < name.length(); i++) {
+            if (!XMLChar.isName(name.charAt(i)) || name.startsWith("_x", i)) {
+                String hex =
+                    "000" + Integer.toHexString(name.charAt(0)).toUpperCase();
+                buffer.append("_x" + hex.substring(hex.length() - 4) + "_");
+            } else {
+                buffer.append(name.charAt(i));
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Escapes the given value according to the rules of section 6.4.4 of
+     * the JSR 170 specification (version 0.16.2).
+     *
+     * @param value original value
+     * @return escaped value
+     */
+    private String escapeValue(String value) {
+        StringBuffer buffer = new StringBuffer();
+
+        for (int i = 1; i < value.length(); i++) {
+            if (value.charAt(i) == ' ') {
+                buffer.append("_x0020_");
+            } else if (value.startsWith("_x", i)) {
+                buffer.append("_x005F_");
+            } else {
+                buffer.append(value.charAt(i));
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Returns the document view representation of the given property.
+     * Multiple values are combined into a space-separated list of
+     * space-escaped string values, binary values are encoded using the
+     * Base64 encoding, and other values are simply returned using their
+     * default string representation.
+     *
+     * @param property the given property
+     * @return document view representation of the property value
+     * @throws RepositoryException on repository errors
+     */
+    private String getValue(Property property) throws RepositoryException {
+        try {
+            if (property.getDefinition().isMultiple()) {
+                StringBuffer buffer = new StringBuffer();
+
+                Value[] values = property.getValues();
+                for (int i = 0; i < values.length; i++) {
+                    if (i > 0) {
+                        buffer.append(" ");
+                    }
+                    if (values[i].getType() == PropertyType.BINARY) {
+                        buffer.append(encodeValue(values[i].getStream()));
+                    } else {
+                        buffer.append(escapeValue(values[i].getString()));
+                    }
+                }
+
+                return buffer.toString();
+            } else if (property.getType() == PropertyType.BINARY) {
+                return encodeValue(property.getStream());
+            } else {
+                return property.getString();
+            }
+        } catch (IOException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+
+    /**
+     * Encodes the given binary stream using Base64 encoding.
+     *
+     * @param input original binary value
+     * @return Base64-encoded value
+     * @throws IOException on IO errors
+     */
+    private String encodeValue(InputStream input) throws IOException {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        byte[] bytes = new byte[4096];
+        for (int n = input.read(bytes); n != -1; n = input.read(bytes)) {
+            buffer.write(bytes, 0, n);
+        }
+        return
+            new String(Base64.encodeBase64(buffer.toByteArray()), "US-ASCII");
+    }
+
+}

Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2005 The Apache Software Foundation or its licensors,
+ *                as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.util.Stack;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * TODO
+ */
+public class DocumentViewImportContentHandler implements ContentHandler {
+
+    private Session session;
+    
+    private Stack stack;
+    
+    private Node node;
+    
+    private StringBuffer text;
+    
+    public DocumentViewImportContentHandler(Node parent) throws RepositoryException {
+        this.session = parent.getSession();
+        this.stack = new Stack();
+        this.node = parent;
+        this.text = new StringBuffer();
+    }
+    
+    
+    private String getName(String uri, String local, String qname)
+            throws RepositoryException {
+        if (uri == null || uri.length() == 0) {
+            return local;
+        }
+        
+        try {
+            return session.getNamespacePrefix(uri) + ":" + local;
+        } catch (NamespaceException ex) {
+            // fall through
+        }
+        
+        int i = qname.indexOf(':');
+        String prefix = (i != -1) ? qname.substring(0, i) : "ext";
+        try {
+            String base = prefix;
+            for (int no = 1; true; prefix = base + no++) {
+                session.getNamespaceURI(prefix);
+            }
+        } catch (NamespaceException ex) {
+            // fall through
+        }
+
+        session.getWorkspace().getNamespaceRegistry()
+            .registerNamespace(prefix, uri);
+        return getName(uri, local, qname);
+    }
+    
+    public void startElement(String uri, String localName, String qName,
+            Attributes atts) throws SAXException {
+        try {
+            importText();
+            
+            stack.push(node);
+
+            node = node.addNode(getName(uri, localName, qName));
+            for (int i = 0; i < atts.getLength(); i++) {
+                String name = getName(atts.getURI(i), atts.getLocalName(i),
+                        atts.getQName(i));
+                node.setProperty(name, atts.getValue(i));
+            }
+        } catch (RepositoryException ex) {
+            throw new SAXException(ex);
+        }
+    }
+
+    /**
+     * TODO
+     * {@inheritDoc}
+     */
+    public void endElement(String uri, String localName, String qName)
+            throws SAXException {
+        try {
+            importText();
+            
+            node = (Node) stack.pop();
+        } catch (RepositoryException ex) {
+            throw new SAXException(ex);
+        }
+    }
+
+    /**
+     * Appends the received characters to the current text buffer.
+     * The accumulated contents of the text buffer is written to an
+     * jcr:xmltext node when an element boundary is reached.  
+     * {@inheritDoc}
+     */
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        text.append(ch, start, length);
+    }
+    
+    /**
+     * Imports the accumulated XML character data as an jcr:xmltext node.
+     * The character data is stored as a jcr:xmlcharacters string property
+     * of the created node. The character data buffer is then cleared.
+     * <p>
+     * This method does nothing if the character data buffer is empty, and
+     * can therefore be invoked whenever an element boundary is reached to
+     * handle the importing of any accumulated character data.   
+     * 
+     * @throws RepositoryException
+     */
+    private void importText() throws RepositoryException {
+        if (text.length() > 0) {
+            Node xmltext = node.addNode("jcr:xmltext");
+            xmltext.setProperty("jcr:xmlcharacters", text.toString());
+            text.setLength(0);
+        }
+    }
+
+    private String unescapeName(String name) {
+        StringBuffer buffer = new StringBuffer();
+        for (int i = name.indexOf("_x"); i != -1; i = name.indexOf("_x")) {
+            buffer.append(name.substring(0, i));
+            if (i + 6 < name.length() && name.charAt(i + 6) == '_') {
+                try {
+                    buffer.append(
+                            (char) Integer.parseInt(name.substring(2, 6), 16));
+                    name = name.substring(i + 7);
+                } catch (NumberFormatException ex) {
+                    buffer.append(name.charAt(i));
+                    name = name.substring(i + 1);
+                }
+            } else {
+                buffer.append(name.charAt(i));
+                name = name.substring(i + 1);
+            }
+        }
+        buffer.append(name);
+        return buffer.toString();
+    }
+
+    
+    /** Ignored. */
+    public void setDocumentLocator(Locator locator) {
+    }
+
+    /** Ignored. */
+    public void startDocument() {
+    }
+
+    /** Ignored. */
+    public void endDocument() throws SAXException {
+    }
+
+    /** Ignored. */
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+    }
+
+    /** Ignored. */
+    public void endPrefixMapping(String prefix) throws SAXException {
+    }
+    
+    /** Ignored. */
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+    }
+
+    /** Ignored. */
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+    }
+
+    /** Ignored. */
+    public void skippedEntity(String name) throws SAXException {
+    }
+
+}

Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2005 The Apache Software Foundation or its licensors,
+ *                as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.ItemVisitor;
+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 org.apache.commons.codec.binary.Base64;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Generic system view exporter for JCR content repositories.
+ * This class can be used to implement the XML system view export
+ * operations using nothing but the standard JCR interfaces. The
+ * export operation is implemented as an ItemVisitor that generates
+ * the system view SAX event stream as it traverses the selected
+ * JCR content tree.
+ * 
+ * <h2>Implementing a customized XML serializer</h2>
+ * <p>
+ * A client can extend this class to provide customized XML serialization
+ * formats. By overriding the protected includeProperty() and includeNode()
+ * methods, a subclass can select which properties and nodes will be included
+ * in the serialized XML stream.
+ * <p>
+ * For example, the following code implements an XML serialization that only
+ * contains the titles of the first two levels of the node tree.
+ * <pre>
+ *     ContentHandler handler = ...;
+ *     Node parent = ...;
+ *     parent.accept(
+ *         new SystemViewExportVisitor(handler, true, false) {
+ *         
+ *             protected boolean includeProperty(Property property)
+ *                     throws RepositoryException {
+ *                 if (property.getName().equals("title")) {
+ *                     return super.includeProperty(property);
+ *                 } else {
+ *                     return false;
+ *                 }
+ *             }
+ *             
+ *             protected boolean includeNode(Node node)
+ *                     throws RepositoryException {
+ *                 return (node.getDepth() <= root.getDepth() + 2);
+ *             }
+ *             
+ *         });
+ * </pre>
+ * 
+ * <h2>Implementing the standard export methods</h2>
+ * <p>
+ * The following is an example of the
+ * Session.exportSysView(String, ContentHandler, boolean, boolean)
+ * method implemented in terms of this exporter class:
+ * <pre>
+ *     public void exportSysView(String absPath, ContentHandler handler,
+ *             boolean skipBinary, boolean noRecurse) throws
+ *             InvalidSerializedDataException, PathNotFoundException,
+ *             SAXException, RepositoryException {
+ *         Item item = getItem(absPath);
+ *         if (item.isNode()) {
+ *             item.accept(new DocumentViewExportVisitor(
+ *                     handler, skipBinary, noRecurse));
+ *         } else {
+ *             throw new PathNotFoundException("Invalid node path: " + path);
+ *         } 
+ *     }
+ * </pre>
+ * <p>
+ * The companion method
+ * Session.exportSysView(String, OutputStream, boolean, boolean)
+ * can be implemented in terms of the above method and the XMLSerializer
+ * class from the Xerces library:
+ * <pre>
+ * import org.apache.xml.serialize.XMLSerializer;
+ * import org.apache.xml.serialize.OutputFormat;
+ * 
+ *     public void exportSysView(String absPath, OutputStream output,
+ *             boolean skipBinary, boolean noRecurse) throws
+ *             InvalidSerializedDataException, PathNotFoundException,
+ *             IOException, RepositoryException {
+ *         try {
+ *             XMLSerializer serializer =
+ *                 new XMLSerializer(output, new OutputFormat());
+ *             exportSysView(absPath, serializer.asContentHandler(),
+ *                     binaryAsLink, noRecurse);
+ *         } catch (SAXException ex) {
+ *             throw new IOException(ex.getMessage());
+ *         }
+ *     }
+ * </pre>
+ * 
+ * @author Jukka Zitting
+ * @see ItemVisitor
+ * @see Session#exportSysView(String, ContentHandler, boolean, boolean)
+ * @see Session#exportSysView(String, java.io.OutputStream, boolean, boolean)
+ */
+public class SystemViewExportVisitor implements ItemVisitor {
+
+    /** The system view namespace URI. */
+    private static final String SV = "http://www.jcp.org/jcr/sv/1.0";
+    
+    /** The special jcr:root node name. */
+    private static final String JCR_ROOT = "jcr:root";
+    
+    /** The special jcr:uuid property name. */
+    private static final String JCR_UUID = "jcr:uuid";
+
+    /** The special jcr:primaryType property name. */
+    private static final String JCR_MIXINTYPES = "jcr:mixinTypes";
+
+    /** The special jcr:mixinTypes property name. */
+    private static final String JCR_PRIMARYTYPE = "jcr:primaryType";
+
+    /** The special sv:node element name. */
+    private static final String SV_NODE = "sv:node";
+    
+    /** Local part of the special sv:node element name. */
+    private static final String NODE = "node";
+    
+    /** The special sv:value element name. */
+    private static final String SV_VALUE = "sv:value";
+    
+    /** Local part of the special sv:value element name. */
+    private static final String VALUE = "value";
+    
+    /** The special sv:property element name. */
+    private static final String SV_PROPERTY = "sv:property";
+    
+    /** Local part of the special sv:property element name. */
+    private static final String PROPERTY = "property";
+
+    /** The special sv:type element name. */
+    private static final String SV_TYPE = "sv:type";
+    
+    /** Local part of the special sv:type element name. */
+    private static final String TYPE = "type";
+    
+    /** The special sv:name element name. */
+    private static final String SV_NAME = "sv:name";
+    
+    /** Local part of the special sv:name element name. */
+    private static final String NAME = "name";
+
+    /**
+     * The SAX content handler for the serialized XML stream.
+     */
+    private ContentHandler handler;
+    
+    /**
+     * Flag to skip all binary properties.
+     */
+    private boolean skipBinary;
+
+    /**
+     * Flag to only serialize the selected node.
+     */
+    private boolean noRecurse;
+    
+    /**
+     * The root node of the serialization tree. This is the node that
+     * is mapped to the root element of the serialized XML stream.
+     */
+    protected Node root;
+
+    /**
+     * Creates an visitor for exporting content using the system view
+     * format. To actually perform the export operation, you need to pass
+     * the visitor instance to the selected content node using the
+     * Node.accept(ItemVisitor) method.
+     * 
+     * @param handler the SAX event handler
+     * @param skipBinary flag for ignoring binary properties 
+     * @param noRecurse flag for not exporting an entire content subtree
+     */
+    public SystemViewExportVisitor(
+            ContentHandler handler, boolean skipBinary, boolean noRecurse) {
+        this.handler = handler;
+        this.skipBinary = skipBinary;
+        this.noRecurse = noRecurse;
+        this.root = null;
+    }
+
+    /**
+     * Exports the visited property using the system view serialization
+     * format. This method generates an sv:property element with appropriate
+     * sv:name and sv:type attributes. The value or values of the node
+     * are included as sv:value sub-elements. 
+     * 
+     * @param property the visited property
+     * @throws RepositoryException on repository errors
+     */
+    public void visit(Property property) throws RepositoryException {
+        try {
+            AttributesImpl attributes = new AttributesImpl();
+            attributes.addAttribute(SV, NAME, SV_NAME,
+                    "CDATA", property.getName());
+            attributes.addAttribute(SV, TYPE, SV_TYPE,
+                    "CDATA", PropertyType.nameFromValue(property.getType()));
+            handler.startElement(SV, PROPERTY, SV_PROPERTY, attributes);
+            
+            if (property.getDefinition().isMultiple()) {
+                Value[] values = property.getValues();
+                for (int i = 0; i < values.length; i++) {
+                    exportValue(values[i]);
+                }
+            } else {
+                exportValue(property.getValue());
+            }
+            
+            handler.endElement(SV, PROPERTY, SV_PROPERTY);
+        } catch (SAXException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+    
+    /**
+     * Exports the visited node using the system view serialization format.
+     * This method is the main entry point to the serialization mechanism.
+     * It manages the opening and closing of the SAX event stream and the
+     * registration of the namespace mappings. The process of actually
+     * generating the document view SAX events is spread into various
+     * private methods, and can be controlled by overriding the protected
+     * includeProperty() and includeNode() methods. 
+     * 
+     * @param node the node to visit
+     * @throws RepositoryException on repository errors
+     */
+    public void visit(Node node) throws RepositoryException {
+        try {
+            // start document
+            if (root == null) {
+                root = node;
+                handler.startDocument();
+                
+                Session session = root.getSession();
+                String[] prefixes = session.getNamespacePrefixes();
+                for (int i = 0; i < prefixes.length; i++) {
+                    handler.startPrefixMapping(prefixes[i],
+                            session.getNamespaceURI(prefixes[i]));
+                }
+            }
+
+            // export current node
+            exportNode(node);
+
+            // end document
+            if (root == node) {
+                handler.endDocument();
+            }
+        } catch (SAXException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+    
+    /**
+     * Checks whether the given property should be included in the XML
+     * serialization. By default this method returns false for the special
+     * jcr:primaryType, jcr:mixinTypes, and jcr:uuid properties that are
+     * required by the system view format. This method also returns false
+     * for all binary properties if the skipBinary flag is set.  
+     * Subclasses can extend this default behaviour to implement more
+     * selective XML serialization.
+     * <p>
+     * To avoid losing the default behaviour described above, subclasses
+     * should always call super.includeProperty(property) instead of
+     * simply returning true for a property.
+     * 
+     * @param property the property to check
+     * @return true if the property should be included, false otherwise
+     * @throws RepositoryException on repository errors
+     */
+    protected boolean includeProperty(Property property)
+            throws RepositoryException {
+        String name = property.getName();
+        return !name.equals(JCR_PRIMARYTYPE)
+            && !name.equals(JCR_MIXINTYPES)
+            && !name.equals(JCR_UUID)
+            && (!skipBinary || property.getType() != PropertyType.BINARY);
+    }
+
+    /**
+     * Checks whether the given node should be included in the XML
+     * serialization. This method returns true by default, but subclasses
+     * can extend this behaviour to implement selective XML serialization.
+     * <p>
+     * Note that this method is only called for the descendants of the
+     * root node of the serialized tree. Also, this method is never called
+     * if the noRecurse flag is set because no descendant nodes will be
+     * serialized anyway.
+     * 
+     * @param node the node to check
+     * @return true if the node should be included, false otherwise
+     * @throws RepositoryException on repository errors
+     */
+    protected boolean includeNode(Node node) throws RepositoryException {
+        return true;
+    }
+    
+    /**
+     * Serializes the given node to the XML stream. This method generates
+     * an sv:node element that contains the node name as the sv:name
+     * attribute. Node properties are included as sv:property elements
+     * and child nodes as other sv:node sub-elements.
+     * 
+     * @param node the given node
+     * @throws SAXException on SAX errors
+     * @throws RepositoryException on repository errors
+     */
+    private void exportNode(Node node) 
+            throws SAXException, RepositoryException {
+        String name = node.getName();
+        if (name.length() == 0) {
+            name = JCR_ROOT;
+        }
+    
+        // Start element
+        AttributesImpl attributes = new AttributesImpl();
+        attributes.addAttribute(SV, NAME, SV_NAME, "CDATA", name);
+        handler.startElement(SV, NODE, SV_NODE, attributes);
+        
+        // Visit the meta-properties
+        node.getProperty(JCR_PRIMARYTYPE).accept(this);
+        if (node.hasProperty(JCR_MIXINTYPES)) {
+            node.getProperty(JCR_MIXINTYPES).accept(this);
+        }
+        if (node.hasProperty(JCR_UUID)) {
+            node.getProperty(JCR_UUID).accept(this);
+        }
+    
+        // Visit other properties
+        PropertyIterator properties = node.getProperties();
+        while (properties.hasNext()) {
+            Property property = properties.nextProperty();
+            if (includeProperty(property)) {
+                property.accept(this);
+            }
+        }
+        
+        // Visit child nodes (unless denied by the noRecurse flag)
+        if (!noRecurse) {
+            NodeIterator children = node.getNodes();
+            while (children.hasNext()) {
+                Node child = children.nextNode();
+                if (includeNode(child)) {
+                    child.accept(this);
+                }
+            }
+        }
+    
+        // End element
+        handler.endElement(SV, NODE, SV_NODE);
+    }
+
+    /**
+     * Serializes the given value to the XML stream. This method generates
+     * an sv:value element and writes the string representation of the
+     * given value as the character content of the element. Binary values
+     * are encoded using the Base64 encoding.
+     * 
+     * @param node the given node
+     * @throws SAXException on SAX errors
+     * @throws RepositoryException on repository errors
+     */
+    private void exportValue(Value value)
+            throws SAXException, RepositoryException {
+        try {
+            handler.startElement(SV, VALUE, SV_VALUE, new AttributesImpl());
+            
+            if (value.getType() != PropertyType.BINARY) {
+                char[] characters = value.getString().toCharArray();
+                handler.characters(characters, 0, characters.length);
+            } else {
+                char[] characters =
+                    encodeValue(value.getStream()).toCharArray();
+                handler.characters(characters, 0, characters.length);
+            }
+            
+            handler.endElement(SV, VALUE, SV_VALUE);
+        } catch (IOException ex) {
+            throw new RepositoryException(ex); 
+        }
+    }
+    
+    /**
+     * Encodes the given binary stream using Base64 encoding.
+     * 
+     * @param input original binary value
+     * @return Base64-encoded value
+     * @throws IOException on IO errors
+     */
+    private String encodeValue(InputStream input) throws IOException {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        byte[] bytes = new byte[4096];
+        for (int n = input.read(bytes); n != -1; n = input.read(bytes)) {
+            buffer.write(bytes, 0, n);
+        }
+        return
+            new String(Base64.encodeBase64(buffer.toByteArray()), "US-ASCII");
+    }
+
+}

Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2005 The Apache Software Foundation or its licensors,
+ *                as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.util.List;
+import java.util.Stack;
+
+import javax.jcr.BooleanValue;
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * TODO
+ */
+public class SystemViewImportContentHandler implements ContentHandler {
+
+    private Session session;
+    
+    private Stack stack;
+    
+    private Node node;
+    
+    private StringBuffer text;
+    
+    private int type;
+    
+    private List values;
+    
+    public SystemViewImportContentHandler(Node parent) throws RepositoryException {
+        this.session = parent.getSession();
+        this.stack = new Stack();
+        this.node = parent;
+        this.text = new StringBuffer();
+    }
+    
+    private String getName(String uri, String local, String qname)
+            throws RepositoryException {
+        if (uri == null || uri.length() == 0) {
+            return local;
+        }
+        
+        try {
+            return session.getNamespacePrefix(uri) + ":" + local;
+        } catch (NamespaceException ex) {
+            // fall through
+        }
+        
+        int i = qname.indexOf(':');
+        String prefix = (i != -1) ? qname.substring(0, i) : "ext";
+        try {
+            String base = prefix;
+            for (int no = 1; true; prefix = base + no++) {
+                session.getNamespaceURI(prefix);
+            }
+        } catch (NamespaceException ex) {
+            // fall through
+        }
+
+        session.getWorkspace().getNamespaceRegistry()
+            .registerNamespace(prefix, uri);
+        return getName(uri, local, qname);
+    }
+    
+    public void startElement(String uri, String localName, String qName,
+            Attributes atts) throws SAXException {
+        try {
+            importText();
+            
+            stack.push(node);
+
+            node = node.addNode(getName(uri, localName, qName));
+            for (int i = 0; i < atts.getLength(); i++) {
+                String name = getName(atts.getURI(i), atts.getLocalName(i),
+                        atts.getQName(i));
+                node.setProperty(name, atts.getValue(i));
+            }
+        } catch (RepositoryException ex) {
+            throw new SAXException(ex);
+        }
+    }
+
+    /**
+     * TODO
+     * {@inheritDoc}
+     */
+    public void endElement(String uri, String localName, String qName)
+            throws SAXException {
+        if (uri.equals("SV") && localName.equals("value")) {
+            String value = text.toString();
+            switch (type) {
+            case PropertyType.BINARY:
+                // TODO values.add(new BinaryValue());
+                break;
+            case PropertyType.BOOLEAN:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.DATE:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.DOUBLE:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.LONG:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.NAME:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.PATH:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.REFERENCE:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            case PropertyType.STRING:
+                values.add(new BooleanValue(Boolean.valueOf(value)));
+                break;
+            default:
+            }
+            
+            text.setLength(0);
+        }
+        try {
+            importText();
+            
+            node = (Node) stack.pop();
+        } catch (RepositoryException ex) {
+            throw new SAXException(ex);
+        }
+    }
+
+    /**
+     * Appends the received characters to the current text buffer.
+     * The accumulated contents of the text buffer is written to an
+     * jcr:xmltext node when an element boundary is reached.  
+     * {@inheritDoc}
+     */
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        text.append(ch, start, length);
+    }
+    
+    /**
+     * Imports the accumulated XML character data as an jcr:xmltext node.
+     * The character data is stored as a jcr:xmlcharacters string property
+     * of the created node. The character data buffer is then cleared.
+     * <p>
+     * This method does nothing if the character data buffer is empty, and
+     * can therefore be invoked whenever an element boundary is reached to
+     * handle the importing of any accumulated character data.   
+     * 
+     * @throws RepositoryException
+     */
+    private void importText() throws RepositoryException {
+        if (text.length() > 0) {
+            Node xmltext = node.addNode("jcr:xmltext");
+            xmltext.setProperty("jcr:xmlcharacters", text.toString());
+            text.setLength(0);
+        }
+    }
+
+    /** Ignored. */
+    public void setDocumentLocator(Locator locator) {
+    }
+
+    /** Ignored. */
+    public void startDocument() {
+    }
+
+    /** Ignored. */
+    public void endDocument() throws SAXException {
+    }
+
+    /** Ignored. */
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+    }
+
+    /** Ignored. */
+    public void endPrefixMapping(String prefix) throws SAXException {
+    }
+    
+    /** Ignored. */
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+    }
+
+    /** Ignored. */
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+    }
+
+    /** Ignored. */
+    public void skippedEntity(String name) throws SAXException {
+    }
+
+}

Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native