Package org.exist.versioning

Source Code of org.exist.versioning.Patch

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2001-07 The eXist Project
*  http://exist-db.org
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program 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 library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*
* $Id$
*/
package org.exist.versioning;

import org.exist.dom.AttrImpl;
import org.exist.dom.DocumentImpl;
import org.exist.dom.ElementImpl;
import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.dom.StoredNode;
import org.exist.dom.NodeSet;
import org.exist.dom.NewArrayNodeSet;
import org.exist.numbering.NodeId;
import org.exist.security.PermissionDeniedException;
import org.exist.security.xacml.AccessContext;
import org.exist.stax.EmbeddedXMLStreamReader;
import org.exist.stax.ExtendedXMLStreamReader;
import org.exist.storage.DBBroker;
import org.exist.util.serializer.AttrList;
import org.exist.util.serializer.Receiver;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQuery;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.Stack;

/**
* Patch a given source document by applying a diff in eXist's diff format.
*/
public class Patch {

    public final static QName ATTR_CHANGE = new QName("change", StandardDiff.NAMESPACE, StandardDiff.PREFIX);
    public final static QName ELEMENT_WRAPPER = new QName("wrapper", StandardDiff.NAMESPACE, StandardDiff.PREFIX);

    public final static String CHANGE_INSERT = "added";
    public final static String CHANGE_APPEND = "appended";
    public final static String CHANGE_DELETED = "deleted";

    private final static String D_START = "start";
    private final static String D_END = "end";
    private final static String D_BOTH = "both";
    private final static String D_SUBTREE = "subtree";
   
    private DBBroker broker;

    private Map<NodeId, String> deletedNodes = null;
    private Map<NodeId, ElementImpl> insertedNodes = null;
    private Map<NodeId, ElementImpl> appendedNodes = null;

    private boolean annotate = false;
    private Stack<QName> elementStack = null;

    private NewArrayNodeSet changeSet = null;
    private DocumentImpl diffDoc;

    /**
     * Create a new Patch instance using the specified broker and diff document.
     *
     * @param broker the DBBroker to use
     * @param diff the diff document to apply
     *
     * @throws XPathException
     */
    public Patch(DBBroker broker, DocumentImpl diff) throws XPathException {
        this.broker = broker;
        this.diffDoc = diff;
        parseDiff(broker, diff);
    }

    /**
     * Apply the diff to the given source data stream passed as an XMLStreamReader. Write
     * output to the specified receiver.
     *
     * @throws DiffException
     */
    public void patch(ExtendedXMLStreamReader reader, Receiver receiver) throws DiffException {
        annotate = false;
        try {
            NodeId skipSubtree = null;
            while (reader.hasNext()) {
                int status = reader.next();
                NodeId nodeId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
                if (status != XMLStreamReader.END_ELEMENT) {
                    ElementImpl insertedNode = insertedNodes.get(nodeId);
                    if (insertedNode != null) {
                        insertNode(insertedNode, receiver, null);
                    }
                } else {
                    ElementImpl appendedNode = appendedNodes.get(nodeId);
                    if (appendedNode != null) {
                        insertNode(appendedNode, receiver, null);
                    }
                }
                String opt = deletedNodes.get(nodeId);
                if (opt == D_SUBTREE) {
                    if (status == XMLStreamReader.START_ELEMENT)
                        skipSubtree = nodeId;
                } else if (opt == D_BOTH) {
                    //skip
                } else if (opt == D_END && status == XMLStreamReader.END_ELEMENT) {
                    // skip
                } else if (opt == D_START && status == XMLStreamReader.START_ELEMENT) {
                    // skip
                } else if (skipSubtree == null)
                    copyNode(reader, receiver, status, false, null);
                if (status == XMLStreamReader.END_ELEMENT && skipSubtree != null &&
                        skipSubtree.equals(nodeId))
                    skipSubtree = null;
            }
        } catch (XMLStreamException e) {
            throw new DiffException("Caught exception while reading source document for patch: " +
                    e.getMessage(), e);
        } catch (IOException e) {
            throw new DiffException("Caught exception while patching document: " + e.getMessage(), e);
        } catch (SAXException e) {
            throw new DiffException("Caught exception while serializing patch output: " + e.getMessage(), e);
        }
    }

    public void annotate(ExtendedXMLStreamReader reader, Receiver receiver) throws DiffException {
        annotate = true;
        elementStack = new Stack<QName>();
        buildChangeSet();
        try {
            NodeId skipSubtree = null;
            while (reader.hasNext()) {
                int status = reader.next();
                NodeId nodeId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
                if (status != XMLStreamReader.END_ELEMENT) {
                    ElementImpl insertedNode = insertedNodes.get(nodeId);
                    if (insertedNode != null) {
                        insertNode(insertedNode, receiver, CHANGE_INSERT);
                    }
                } else {
                    ElementImpl appendedNode = appendedNodes.get(nodeId);
                    if (appendedNode != null) {
                        insertNode(appendedNode, receiver, CHANGE_APPEND);
                    }
                }
                boolean skip = false;
                String opt = deletedNodes.get(nodeId);
                if (opt != null) {
                    if (opt == D_SUBTREE) {
                        if (status == XMLStreamReader.START_ELEMENT)
                            skipSubtree = nodeId;
                        skip = true;
                    } else if (opt == D_BOTH ||
                            (opt == D_END && status == XMLStreamReader.END_ELEMENT) ||
                            (opt == D_START && status == XMLStreamReader.START_ELEMENT)) {
                        skip = true;
                    }
                }
                if (annotate || (!skip && skipSubtree == null))
                    copyNode(reader, receiver, status, skip || (skipSubtree != null && skipSubtree == nodeId), CHANGE_DELETED);
                if (status == XMLStreamReader.END_ELEMENT && skipSubtree != null &&
                        skipSubtree.equals(nodeId))
                    skipSubtree = null;
            }
        } catch (XMLStreamException e) {
            throw new DiffException("Caught exception while reading source document for patch: " +
                    e.getMessage(), e);
        } catch (IOException e) {
            throw new DiffException("Caught exception while patching document: " + e.getMessage(), e);
        } catch (SAXException e) {
            throw new DiffException("Caught exception while serializing patch output: " + e.getMessage(), e);
        }
        changeSet = null;
    }

    private void insertNode(StoredNode insertedNode, Receiver receiver, String changeMessage) throws XMLStreamException, IOException, SAXException {
        ExtendedXMLStreamReader reader = broker.newXMLStreamReader(insertedNode, false);
        reader.next();
        int treeLevel = 0;
        while (reader.hasNext()) {
            int status = reader.next();
            if ((status == XMLStreamReader.START_ELEMENT || status == XMLStreamReader.END_ELEMENT) &&
                    StandardDiff.NAMESPACE.equals(reader.getNamespaceURI())) {
                if (status == XMLStreamReader.START_ELEMENT) {
                    if ("attribute".equals(reader.getLocalName())) {
                        int attrCount = reader.getAttributeCount();
                        for (int i = 0; i < attrCount; i++) {
                            QName qname = reader.getAttributeQName(i);
                            receiver.attribute(qname, reader.getAttributeValue(i));
                        }
                    } else if ("comment".equals(reader.getLocalName())) {
                        StringBuffer buf = new StringBuffer();
                        while (reader.hasNext()) {
                            status = reader.next();
                            if (status == XMLStreamReader.END_ELEMENT &&
                                    reader.getNamespaceURI().equals(StandardDiff.NAMESPACE) &&
                                    reader.getLocalName().equals("comment"))
                                break;
                            if (status == XMLStreamReader.CHARACTERS)
                                buf.append(reader.getText());
                        }
                        char[] ch = buf.toString().toCharArray();
                        receiver.comment(ch, 0, ch.length);
                    } else if ("start".equals(reader.getLocalName())) {
                        String namespace = reader.getAttributeValue("", "namespace");
                        String name = reader.getAttributeValue("", "name");
                        receiver.startElement(new QName(QName.extractLocalName(name), namespace, QName.extractPrefix(name)), null);
                        if (annotate)
                            receiver.attribute(ATTR_CHANGE, "tag-" + changeMessage);
                    } else if ("end".equals(reader.getLocalName())) {
                        String namespace = reader.getAttributeValue("", "namespace");
                        String name = reader.getAttributeValue("", "name");
                        receiver.endElement(new QName(QName.extractLocalName(name), namespace, QName.extractPrefix(name)));
                    }
                }
            } else {
                copyNode(reader, receiver, status, treeLevel == 0, changeMessage);
                if (status == XMLStreamReader.START_ELEMENT)
                    treeLevel++;
                else if (status == XMLStreamReader.END_ELEMENT)
                    treeLevel--;
            }
        }
    }

    private void copyNode(ExtendedXMLStreamReader reader, Receiver receiver, int status,
                          boolean onFirstNode, String changeMessage) throws SAXException, XMLStreamException, IOException {
        AttrList attrs;
        switch (status) {
            case XMLStreamReader.START_ELEMENT:
                attrs = new AttrList();
                if (annotate) {
                    if (onFirstNode)
                        attrs.addAttribute(ATTR_CHANGE, changeMessage);
                    else {
                        NodeId nodeId = (NodeId) reader.getProperty(EmbeddedXMLStreamReader.PROPERTY_NODE_ID);
                        NodeSet children = changeSet.selectParentChild(new NodeProxy(diffDoc, nodeId), NodeSet.ANCESTOR);
                        if (children != null && !children.isEmpty())
                            attrs.addAttribute(ATTR_CHANGE, "changed");
                    }
                    if (elementStack.size() == 0)
                        receiver.startPrefixMapping(StandardDiff.PREFIX, StandardDiff.NAMESPACE);
                }

                for (int i = 0; i < reader.getAttributeCount(); i++) {
                    // check if an attribute has to be inserted before the current attribute
                    NodeId nodeId = reader.getAttributeId(i);

                    // check if an attribute has to be inserted before the current attribute
                    ElementImpl insertedNode = insertedNodes.get(nodeId);
                    if (insertedNode != null) {
                        StoredNode child = (StoredNode) insertedNode.getFirstChild();
                        while (child != null) {
                            if (StandardDiff.NAMESPACE.equals(child.getNamespaceURI()) && "attribute".equals(child.getLocalName())) {
                                NamedNodeMap map = child.getAttributes();
                                for (int j = 0; j < map.getLength(); j++) {
                                    AttrImpl attr = (AttrImpl) map.item(j);
                                    if (!attr.getName().startsWith("xmlns"))
                                        attrs.addAttribute(attr.getQName(), attr.getValue(),
                                                attr.getType(), attr.getNodeId());
                                }
                            }
                            child = (StoredNode) child.getNextSibling();
                        }
                    }

                    if (deletedNodes.get(nodeId) == null) {
                        QName attrQn = new QName(reader.getAttributeLocalName(i), reader.getAttributeNamespace(i),
                                reader.getAttributePrefix(i));
                        attrs.addAttribute(
                                attrQn,
                                reader.getAttributeValue(i),
                                getAttributeType(reader.getAttributeType(i))
                        );
                    }
                }
                QName qn = new QName(reader.getLocalName(), reader.getNamespaceURI(), reader.getPrefix());
                receiver.startElement(qn, attrs);
                if (elementStack != null)
                    elementStack.push(qn);
                break;
            case XMLStreamReader.END_ELEMENT:
                receiver.endElement(new QName(reader.getLocalName(), reader.getNamespaceURI(), reader.getPrefix()));
                if (elementStack != null) {
                    if (elementStack.isEmpty())
                        receiver.endPrefixMapping(StandardDiff.PREFIX);
                    elementStack.pop();
                }
                break;
            case XMLStreamReader.CHARACTERS:
                if (onFirstNode && annotate) {
                    attrs = new AttrList();
                    attrs.addAttribute(ATTR_CHANGE, changeMessage);
                    receiver.startElement(ELEMENT_WRAPPER, attrs);
                }
                receiver.characters(reader.getText());
                if (onFirstNode && annotate)
                    receiver.endElement(ELEMENT_WRAPPER);
                break;
            case XMLStreamReader.CDATA:
                if (onFirstNode && annotate)
                    receiver.startElement(ELEMENT_WRAPPER, null);
                char[] cdata = reader.getTextCharacters();
                receiver.cdataSection(cdata, 0, cdata.length);
                if (onFirstNode && annotate)
                    receiver.endElement(ELEMENT_WRAPPER);
                break;
            case XMLStreamReader.PROCESSING_INSTRUCTION:
                receiver.processingInstruction(reader.getPITarget(), reader.getPIData());
                break;
            case XMLStreamReader.COMMENT:
                char[] ch = reader.getTextCharacters();
                receiver.comment(ch, 0, ch.length);
                break;
        }
    }

    private void parseDiff(DBBroker broker, DocumentImpl doc) throws XPathException {
        deletedNodes = new TreeMap<NodeId, String>();
        insertedNodes = new TreeMap<NodeId, ElementImpl>();
        appendedNodes = new TreeMap<NodeId, ElementImpl>();
        XQuery service = broker.getXQueryService();
        Sequence changes;
    try {
      changes = service.execute("declare namespace v=\"http://exist-db.org/versioning\";" +
              "doc('" + doc.getURI().toString() + "')/v:version/v:diff/*",
              Sequence.EMPTY_SEQUENCE, AccessContext.TEST);
    } catch (PermissionDeniedException e) {
      throw new XPathException(e);
    }
        for (SequenceIterator i = changes.iterate(); i.hasNext(); ) {
            NodeProxy p = (NodeProxy) i.nextItem();
            Element child = (Element) p.getNode();
            if (child.getNodeType() == Node.ELEMENT_NODE &&
                    child.getNamespaceURI().equals(StandardDiff.NAMESPACE)) {
               NodeId id = parseRef(broker, child, "ref");
               if (child.getLocalName().equals("delete")) {
                   String event = child.getAttribute("event");
                   if (event == null || event.length() == 0)
                       deletedNodes.put(id, D_SUBTREE);
                   else if ("both".equals(event))
                       deletedNodes.put(id, D_BOTH);
                   else if ("start".equals(event)) {
                       String opt = deletedNodes.get(id);
                       if (opt == D_END)
                           deletedNodes.put(id, D_BOTH);
                       else
                           deletedNodes.put(id, D_START);
                   } else {
                       String opt = deletedNodes.get(id);
                       if (opt == D_START)
                           deletedNodes.put(id, D_BOTH);
                       else
                           deletedNodes.put(id, D_END);
                   }
               } else if (child.getLocalName().equals("insert")) {
                   insertedNodes.put(id, (ElementImpl)child);
               } else if (child.getLocalName().equals("append")) {
                   appendedNodes.put(id, (ElementImpl)child);
               }
            }
        }
    }

    private NodeId parseRef(DBBroker broker, Node child, String attr) {
        String idval = ((Element)child).getAttribute(attr);
        return broker.getBrokerPool().getNodeFactory().createFromString(idval);
    }

    private int getAttributeType(String attributeType) {
        if ("ID".equals(attributeType))
            return AttrImpl.ID;
        else if ("IDREF".equals(attributeType))
            return AttrImpl.IDREF;
        else if ("IDREFS".equals(attributeType))
            return AttrImpl.IDREFS;
        else
            return AttrImpl.CDATA;
    }

    private void buildChangeSet() {
        changeSet = new NewArrayNodeSet();
        for (NodeId nodeId : insertedNodes.keySet()) {
            changeSet.add(new NodeProxy(diffDoc, nodeId));
        }
        for (NodeId nodeId : appendedNodes.keySet()) {
            changeSet.add(new NodeProxy(diffDoc, nodeId));
        }
        for (NodeId nodeId : deletedNodes.keySet()) {
            changeSet.add(new NodeProxy(diffDoc, nodeId));
        }
    }
}
TOP

Related Classes of org.exist.versioning.Patch

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.