Package org.jboss.dna.jcr

Source Code of org.jboss.dna.jcr.JcrDocumentViewExporter

/*
* JBoss DNA (http://www.jboss.org/dna)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.  Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* JBoss DNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dna.jcr;

import java.io.IOException;
import java.io.OutputStream;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
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.Value;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.common.text.TextEncoder;
import org.jboss.dna.common.text.XmlNameEncoder;
import org.jboss.dna.common.util.Base64;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.property.Name;
import org.jboss.dna.graph.property.ValueFactories;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
* Implementation of {@link AbstractJcrExporter} that implements the document view mapping described in section 6.4.2 of the JCR
* 1.0 specification.
*
* @see JcrSession#exportDocumentView(String, ContentHandler, boolean, boolean)
* @see JcrSession#exportDocumentView(String, OutputStream, boolean, boolean)
*/
@NotThreadSafe
class JcrDocumentViewExporter extends AbstractJcrExporter {

    private static final int ENCODE_BUFFER_SIZE = 2 << 15;

    private static final TextEncoder VALUE_ENCODER = new JcrDocumentViewExporter.JcrDocumentViewPropertyEncoder();

    JcrDocumentViewExporter( JcrSession session ) {
        super(session, Collections.<String>emptyList());
    }

    /**
     * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
     * <code>contentHandler</code>.
     *
     * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
     *        constructor, the entire subtree rooted at <code>node</code> will be exported.
     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
     * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
     *        and not any of its child nodes.
     * @throws SAXException if an exception occurs during generation of the XML document
     * @throws RepositoryException if an exception occurs accessing the content repository
     */
    @Override
    public void exportNode( Node node,
                            ContentHandler contentHandler,
                            boolean skipBinary,
                            boolean noRecurse ) throws RepositoryException, SAXException {
        ExecutionContext executionContext = session.getExecutionContext();

        // If this node is a special xmltext node, output it as raw content (see JCR 1.0 spec - section 6.4.2.3
        if (node.getParent() != null && isXmlTextNode(node)) {

            String xmlCharacters = getXmlCharacters(node);
            contentHandler.characters(xmlCharacters.toCharArray(), 0, xmlCharacters.length());

            return;
        }

        ValueFactories valueFactories = executionContext.getValueFactories();
        AttributesImpl atts = new AttributesImpl();

        // Build the attributes for this node's element
        PropertyIterator properties = node.getProperties();
        while (properties.hasNext()) {
            Property prop = properties.nextProperty();

            Name propName = ((AbstractJcrProperty)prop).name();

            String localPropName = getPrefixedName(propName);

            if (skipBinary && PropertyType.BINARY == prop.getType()) {
                atts.addAttribute(propName.getNamespaceUri(),
                                  propName.getLocalName(),
                                  localPropName,
                                  PropertyType.nameFromValue(prop.getType()),
                                  "");
                continue;
            }

            Value value;
            if (prop instanceof JcrSingleValueProperty) {
                value = prop.getValue();
            } else {
                // Only output the first value of the multi-valued property. This is acceptable as per JCR 1.0 Spec - section
                // 6.4.2.5
                value = prop.getValues()[0];
            }

            String valueAsString;
            if (PropertyType.BINARY == prop.getType()) {
                StringBuffer buff = new StringBuffer(ENCODE_BUFFER_SIZE);
                try {
                    Base64.InputStream is = new Base64.InputStream(value.getStream(), Base64.ENCODE);

                    byte[] bytes = new byte[ENCODE_BUFFER_SIZE];
                    int len;
                    while (-1 != (len = is.read(bytes, 0, ENCODE_BUFFER_SIZE))) {
                        buff.append(new String(bytes, 0, len));
                    }
                } catch (IOException ioe) {
                    throw new RepositoryException(ioe);
                }
                valueAsString = buff.toString();
            } else {
                valueAsString = VALUE_ENCODER.encode(value.getString());
            }

            atts.addAttribute(propName.getNamespaceUri(),
                              propName.getLocalName(),
                              localPropName,
                              PropertyType.nameFromValue(prop.getType()),
                              valueAsString);
        }

        Name name;

        // Special case to stub in name for root node as per JCR 1.0 Spec - 6.4.2.2
        if ("/".equals(node.getPath())) {
            name = JcrLexicon.ROOT;
        } else {
            name = valueFactories.getNameFactory().create(node.getName());
        }

        startElement(contentHandler, name, atts);

        if (!noRecurse) {
            NodeIterator nodes = node.getNodes();
            while (nodes.hasNext()) {
                exportNode(nodes.nextNode(), contentHandler, skipBinary, noRecurse);
            }
        }

        endElement(contentHandler, name);

    }

    /**
     * Indicates whether the current node is an XML text node as per section 6.4.2.3 of the JCR 1.0 specification. XML text nodes
     * are nodes that have the name &quot;jcr:xmltext&quot; and only one property (besides the mandatory
     * &quot;jcr:primaryType&quot;). The property must have a property name of &quot;jcr:xmlcharacters&quot;, a type of
     * <code>String</code>, and does not have multiple values.
     * <p/>
     * In practice, this is handled in DNA by making XML text nodes have a type of &quot;dna:xmltext&quot;, which enforces these
     * property characteristics.
     *
     * @param node the node to test
     * @return whether this node is a special xml text node
     * @throws RepositoryException if there is an error accessing the repository
     */
    private boolean isXmlTextNode( Node node ) throws RepositoryException {
        // ./xmltext/xmlcharacters exception (see JSR-170 Spec 6.4.2.3)

        if (getPrefixedName(JcrLexicon.XMLTEXT).equals(node.getName())) {
            if (node.getNodes().getSize() == 0) {

                PropertyIterator properties = node.getProperties();
                boolean xmlCharactersFound = false;

                while (properties.hasNext()) {
                    Property property = properties.nextProperty();

                    if (getPrefixedName(JcrLexicon.PRIMARY_TYPE).equals(property.getName())) {
                        continue;
                    }

                    if (getPrefixedName(JcrLexicon.XMLCHARACTERS).equals(property.getName())) {
                        xmlCharactersFound = true;
                        continue;
                    }

                    // If the xmltext node has any properties other than primaryType or xmlcharacters, return false;
                    return false;
                }

                return xmlCharactersFound;
            }
        }

        return false;

    }

    /**
     * Returns the XML characters for the given node. The node must be an XML text node, as defined in
     * {@link #isXmlTextNode(Node)}.
     *
     * @param node the node for which XML characters will be retrieved.
     * @return the xml characters for this node
     * @throws RepositoryException if there is an error accessing this node
     */
    private String getXmlCharacters( Node node ) throws RepositoryException {
        // ./xmltext/xmlcharacters exception (see JSR-170 Spec 6.4.2.3)

        assert isXmlTextNode(node);

        Property xmlCharacters = node.getProperty(getPrefixedName(JcrLexicon.XMLCHARACTERS));

        assert xmlCharacters != null;

        if (xmlCharacters.getDefinition().isMultiple()) {
            StringBuffer buff = new StringBuffer();

            for (Value value : xmlCharacters.getValues()) {
                buff.append(value.getString());
            }

            return buff.toString();
        }

        return xmlCharacters.getValue().getString();
    }

    /**
     * Special {@link TextEncoder} that implements the subset of XML name encoding suggested by section 6.4.4 of the JCR 1.0.1
     * specification. This encoder only encodes space (0x20), carriage return (0x0D), new line (0x0A), tab (0x09), and any
     * underscore characters that might otherwise suggest an encoding, as defined in {@link XmlNameEncoder}.
     */
    protected static class JcrDocumentViewPropertyEncoder extends XmlNameEncoder {

        private static final Set<Character> MAPPED_CHARACTERS;

        static {
            MAPPED_CHARACTERS = new HashSet<Character>();
            MAPPED_CHARACTERS.add(' ');
            MAPPED_CHARACTERS.add('\r');
            MAPPED_CHARACTERS.add('\n');
            MAPPED_CHARACTERS.add('\t');

        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.common.text.TextEncoder#encode(java.lang.String)
         */
        // See section 6.4.4 of the JCR 1.0.1 spec for why these hoops must be jumped through
        @Override
        public String encode( String text ) {
            if (text == null) return null;
            if (text.length() == 0) return text;
            StringBuilder sb = new StringBuilder();
            String hex = null;
            CharacterIterator iter = new StringCharacterIterator(text);
            for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
                if (c == '_') {
                    // Read the next character (if there is one) ...
                    char next = iter.next();
                    if (next == CharacterIterator.DONE) {
                        sb.append(c);
                        break;
                    }
                    // If the next character is not 'x', then these are just regular characters ...
                    if (next != 'x') {
                        sb.append(c).append(next);
                        continue;
                    }
                    // The next character is 'x', so write out the '_' character in encoded form ...
                    sb.append("_x005f_");
                    // And then write out the next character ...
                    sb.append(next);
                } else if (!MAPPED_CHARACTERS.contains(c)) {
                    // Legal characters for an XML Name ...
                    sb.append(c);
                } else {
                    // All other characters must be escaped with '_xHHHH_' where 'HHHH' is the hex string for the code point
                    hex = Integer.toHexString(c);
                    // The hex string excludes the leading '0's, so check the character values so we know how many to prepend
                    if (c >= '\u0000' && c <= '\u000f') {
                        sb.append("_x000").append(hex);
                    } else if (c >= '\u0010' && c <= '\u00ff') {
                        sb.append("_x00").append(hex);
                    } else if (c >= '\u0100' && c <= '\u0fff') {
                        sb.append("_x0").append(hex);
                    } else {
                        sb.append("_x").append(hex);
                    }
                    sb.append('_');
                }
            }
            return sb.toString();
        }
    }
}
TOP

Related Classes of org.jboss.dna.jcr.JcrDocumentViewExporter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.