All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.JcrDocumentViewExporter Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * ModeShape (http://www.modeshape.org)
 *
 * 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.modeshape.jcr;

import java.io.IOException;
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 org.modeshape.common.annotation.NotThreadSafe;
import org.modeshape.common.text.TextDecoder;
import org.modeshape.common.text.TextEncoder;
import org.modeshape.common.text.XmlNameEncoder;
import org.modeshape.common.util.Base64;
import org.modeshape.common.util.IoUtil;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.ValueFactories;
import org.modeshape.jcr.value.ValueFactory;
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, java.io.OutputStream, boolean, boolean)
 */
@NotThreadSafe
class JcrDocumentViewExporter extends AbstractJcrExporter {

    /** The name encoder needs to encode spaces plus the standard slash characters */
    static final TextEncoder NAME_ENCODER = new JcrDocumentViewExporter.JcrDocumentViewPropertyEncoder(' ', '\r', '\n', '\t');
    static final TextEncoder VALUE_ENCODER = NAME_ENCODER;
    static final TextDecoder NAME_DECODER = (TextDecoder)NAME_ENCODER;
    static final TextDecoder VALUE_DECODER = (TextDecoder)NAME_ENCODER;

    private final ValueFactory stringFactory;

    JcrDocumentViewExporter( JcrSession session ) {
        super(session, Collections.emptyList());
        stringFactory = session.stringFactory();
    }

    /**
     * Exports node (or the subtree rooted at node) into an XML document by invoking SAX events on
     * contentHandler.
     * 
     * @param node the node which should be exported. If noRecursion was set to false in the
     *        constructor, the entire subtree rooted at node 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 true, indicates that binary properties should not be exported
     * @param noRecurse iftrue, 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.context();

        JcrSharedNode sharedNode = asSharedNode(node);
        if (sharedNode != null) {
            // This is a shared node, and per Section 14.7 of the JCR 2.0 specification, they have to be written out
            // in a special way ...
            AttributesImpl atts = new AttributesImpl();

            // jcr:primaryType = nt:share ...
            addAttribute(atts, JcrLexicon.PRIMARY_TYPE, PropertyType.NAME, JcrNtLexicon.SHARE);

            // jcr:uuid = UUID of shared node ...
            addAttribute(atts, JcrLexicon.UUID, PropertyType.STRING, node.getIdentifier());

            // Write out the element ...
            Name name = sharedNode.segment().getName();
            startElement(contentHandler, name, atts);
            endElement(contentHandler, name);
            return;
        }
        exporting(node);

        // 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.getDepth() > 0 && isXmlTextNode(node)) {
            String xmlCharacters = getXmlCharacters(node);
            contentHandler.characters(xmlCharacters.toCharArray(), 0, xmlCharacters.length());
            return;
        }

        // Build the attributes for this node's element, but add the primary type first ...
        AttributesImpl atts = new AttributesImpl();
        Property primaryType = ((AbstractJcrNode)node).getProperty(JcrLexicon.PRIMARY_TYPE);
        if (primaryType != null) {
            addAttribute(atts, primaryType, skipBinary, false);
        }

        // And add the remaining properties next ...
        PropertyIterator properties = node.getProperties();
        while (properties.hasNext()) {
            Property prop = properties.nextProperty();
            if (prop == primaryType) continue;
            addAttribute(atts, prop, skipBinary, true);
        }

        // Special case to stub in name for root node as per JCR 1.0 Spec - 6.4.2.2
        Name name = null;
        ValueFactories valueFactories = executionContext.getValueFactories();
        if (node.getDepth() == 0) {
            name = JcrLexicon.ROOT;
        } else {
            name = valueFactories.getNameFactory().create(node.getName());
        }

        // Write out the element ...
        startElement(contentHandler, name, atts);
        if (!noRecurse) {
            //the node iterator should check permissions and return only those nodes on which there is READ permission
            NodeIterator nodes = node.getNodes();
            while (nodes.hasNext()) {
                Node child = nodes.nextNode();
                //MODE-2171 Ignore any ACL nodes
                if (!child.isNodeType(ModeShapeLexicon.ACCESS_LIST_NODE_TYPE_STRING)) {
                    exportNode(child, contentHandler, skipBinary, noRecurse);
                }
            }
        }
        endElement(contentHandler, name);
    }

    protected void addAttribute( AttributesImpl atts,
                                 Name propertyName,
                                 int propertyType,
                                 Object value ) {
        String valueAsString = VALUE_ENCODER.encode(stringFactory.create(value));
        String localPropName = getPrefixedName(propertyName);
        atts.addAttribute(propertyName.getNamespaceUri(),
                          propertyName.getLocalName(),
                          localPropName,
                          org.modeshape.jcr.api.PropertyType.nameFromValue(propertyType),
                          valueAsString);

    }

    protected void addAttribute( AttributesImpl atts,
                                 Property prop,
                                 boolean skipBinary,
                                 boolean skipPrimaryType ) throws RepositoryException {

        Name propName = ((AbstractJcrProperty)prop).name();
        if (skipPrimaryType && JcrLexicon.PRIMARY_TYPE.equals(propName)) return;

        String localPropName = getPrefixedName(propName);

        if (skipBinary && PropertyType.BINARY == prop.getType()) {
            atts.addAttribute(propName.getNamespaceUri(),
                              propName.getLocalName(),
                              localPropName,
                              org.modeshape.jcr.api.PropertyType.nameFromValue(prop.getType()),
                              "");
            return;
        }

        Value value = null;
        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[] values = prop.getValues();
            value = values.length > 0 ? values[0] : null;
        }

        String valueAsString = "";
        if (value != null) {
            if (PropertyType.BINARY == prop.getType()) {
                try {
                    Base64.InputStream is = new Base64.InputStream(value.getBinary().getStream(), Base64.ENCODE);
                    valueAsString = IoUtil.read(is);
                } catch (IOException ioe) {
                    throw new RepositoryException(ioe);
                }
            } else {
                valueAsString = VALUE_ENCODER.encode(value.getString());
            }
        }

        atts.addAttribute(propName.getNamespaceUri(),
                          NAME_ENCODER.encode(propName.getLocalName()),
                          NAME_ENCODER.encode(localPropName),
                          org.modeshape.jcr.api.PropertyType.nameFromValue(prop.getType()),
                          valueAsString);
    }

    /**
     * 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 "jcr:xmltext" and only one property (besides the mandatory
     * "jcr:primaryType"). The property must have a property name of "jcr:xmlcharacters", a type of
     * String, and does not have multiple values.
     * 

* In practice, this is handled in ModeShape by making XML text nodes have a type of "dna:xmltext", 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()) { StringBuilder bf = new StringBuilder(); for (Value value : xmlCharacters.getValues()) { bf.append(value.getString()); } return bf.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 final Set mappedCharacters; protected JcrDocumentViewPropertyEncoder( char... chars ) { mappedCharacters = new HashSet(); for (char c : chars) { mappedCharacters.add(c); } } // 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); char first = iter.first(); boolean isDigit = Character.isDigit(first); for (char c = first; c != CharacterIterator.DONE; c = iter.next()) { if (c != first && isDigit && !Character.isDigit(c)) { isDigit = false; } 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 (!mappedCharacters.contains(c) && !isDigit) { // 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(); } @Override public String decode( String encodedText ) { return super.decode(encodedText); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy