/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2007 The eXist team
* 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 program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.dom;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import org.exist.EXistException;
import org.exist.Namespaces;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.exist.indexing.StreamListener;
import org.exist.numbering.NodeId;
import org.exist.stax.EmbeddedXMLStreamReader;
import org.exist.stax.ExtendedXMLStreamReader;
import org.exist.storage.DBBroker;
import org.exist.storage.ElementValue;
import org.exist.storage.NodePath;
import org.exist.storage.RangeIndexSpec;
import org.exist.storage.Signatures;
import org.exist.storage.btree.Value;
import org.exist.storage.txn.TransactionException;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.ByteArrayPool;
import org.exist.util.ByteConversion;
import org.exist.util.UTF8;
import org.exist.util.pool.NodePool;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
import org.exist.xquery.value.StringValue;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.TypeInfo;
import org.w3c.dom.UserDataHandler;
/**
* ElementImpl.java
*
* @author Wolfgang Meier
*/
public class ElementImpl extends NamedNode implements Element, ElementAtExist {
public static final int LENGTH_ELEMENT_CHILD_COUNT = 4; //sizeof int
public static final int LENGTH_ATTRIBUTES_COUNT = 2; //sizeof short
public static final int LENGTH_NS_ID = 2; //sizeof short
public static final int LENGTH_PREFIX_LENGTH = 2; //sizeof short
private short attributes = 0;
private int children = 0;
private int position = 0;
private Map<String, String> namespaceMappings = null;
private int indexType = RangeIndexSpec.NO_INDEX;
private boolean preserveWS = false;
private boolean isDirty = false;
public ElementImpl() {
super(Node.ELEMENT_NODE);
}
/**
* Constructor for the ElementImpl object
*
* @param nodeName Description of the Parameter
*/
public ElementImpl(QName nodeName, SymbolTable symbols) throws DOMException {
super(Node.ELEMENT_NODE, nodeName);
this.nodeName = nodeName;
if (symbols.getSymbol(nodeName.getLocalName()) < 0) {
throw new DOMException(DOMException.INVALID_ACCESS_ERR,
"Too many element/attribute names registered in the database. No of distinct names is limited to 16bit. Aborting store.");
}
}
public ElementImpl(ElementImpl other) {
super(other);
this.children = other.children;
this.attributes = other.attributes;
this.namespaceMappings = other.namespaceMappings;
this.indexType = other.indexType;
this.position = other.position;
}
/**
* Reset this element to its initial state.
*
*/
@Override
public void clear() {
super.clear();
attributes = 0;
children = 0;
position = 0;
namespaceMappings = null;
//TODO : reset below as well ? -pb
//indexType
//preserveWS
}
public void setIndexType(int idxType) {
this.indexType = idxType;
}
public int getIndexType() {
return indexType;
}
@Override
public boolean isDirty() {
return isDirty;
}
@Override
public void setDirty(boolean dirty) {
this.isDirty = dirty;
}
public void setPosition(int position) {
this.position = position;
}
public int getPosition() {
return position;
}
public boolean declaresNamespacePrefixes() {
if (namespaceMappings == null)
{return false;}
return (namespaceMappings.size() > 0);
}
/**
* Serializes a (persistent DOM) Element to a byte array
*
* data = signature childCount nodeIdUnitsLength nodeId attributesCount localNameId namespace? prefixData?
*
* signature = 0x20 | localNameLength | hasNamespace? | isDirty?
*
* localNameLength = noContent OR intContent OR shortContent OR byteContent
* noContent = 0x0
* intContent = 0x1
* shortContent = 0x2
* byteContent = 0x3
*
* hasNamespace = 0x10
*
* isDirty = 0x8
*
* childCount = [int] (4 bytes) The number of child nodes
* nodeIdUnitsLength = [short] (2 bytes) The number of units of the element's NodeId
* nodeId = @see org.exist.numbering.DLNBase#serialize(byte[], int)
* attributesCount = [short] (2 bytes) The number of attributes
*
* localNameId = [int] (4 bytes) | [short] (2 bytes) | [byte] 1 byte. The Id of the element's local name from SymbolTable (symbols.dbx)
*
* namespace = namespaceUriId namespacePrefixLength elementNamespacePrefix?
* namespaceUriId = [short] (2 bytes) The Id of the namespace URI from SymbolTable (symbols.dbx)
* namespacePrefixLength = [short] (2 bytes)
* elementNamespacePrefix = eUtf8
*
* eUtf8 = @see org.exist.util.UTF8#encode(java.lang.String, byte[], int)
*
* prefixData = namespaceMappingsCount namespaceMapping+
* namespaceMappingsCount = [short] (2 bytes)
* namespaceMapping = namespacePrefix namespaceUriId
* namespacePrefix = jUtf8
*
* jUtf8 = @see java.io.DataOutputStream#writeUTF(java.lang.String)
*/
@Override
public byte[] serialize() {
if (nodeId == null)
{throw new RuntimeException("nodeId = null for element: " +
getQName().getStringValue());}
try {
final SymbolTable symbols = ownerDocument.getBrokerPool().getSymbols();
byte[] prefixData = null;
// serialize namespace prefixes declared in this element
if (declaresNamespacePrefixes()) {
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final DataOutputStream out = new DataOutputStream(bout);
out.writeShort(namespaceMappings.size());
for (final Iterator<Map.Entry<String, String>> i =
namespaceMappings.entrySet().iterator(); i.hasNext();) {
final Map.Entry<String, String> entry = i.next();
out.writeUTF(entry.getKey());
final short nsId = symbols.getNSSymbol(entry.getValue());
out.writeShort(nsId);
}
prefixData = bout.toByteArray();
}
final short id = symbols.getSymbol(this);
final boolean hasNamespace = nodeName.needsNamespaceDecl();
short nsId = 0;
if (hasNamespace)
{nsId = symbols.getNSSymbol(nodeName.getNamespaceURI());}
final byte idSizeType = Signatures.getSizeType(id);
byte signature = (byte) ((Signatures.Elem << 0x5) | idSizeType);
int prefixLen = 0;
if (hasNamespace) {
if (nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0)
{prefixLen = UTF8.encoded(nodeName.getPrefix());}
signature |= 0x10;
}
if (isDirty)
{signature |= 0x8;}
final int nodeIdLen = nodeId.size();
final byte[] data =
ByteArrayPool.getByteArray(
StoredNode.LENGTH_SIGNATURE_LENGTH + LENGTH_ELEMENT_CHILD_COUNT +
NodeId.LENGTH_NODE_ID_UNITS +
nodeIdLen + LENGTH_ATTRIBUTES_COUNT +
Signatures.getLength(idSizeType) +
(hasNamespace ? prefixLen + 4 : 0) +
(prefixData != null ? prefixData.length : 0)
);
int next = 0;
data[next] = signature;
next += StoredNode.LENGTH_SIGNATURE_LENGTH;
ByteConversion.intToByte(children, data, next);
next += LENGTH_ELEMENT_CHILD_COUNT;
ByteConversion.shortToByte((short) nodeId.units(), data, next);
next += NodeId.LENGTH_NODE_ID_UNITS;
nodeId.serialize(data, next);
next += nodeIdLen;
ByteConversion.shortToByte(attributes, data, next);
next += LENGTH_ATTRIBUTES_COUNT;
Signatures.write(idSizeType, id, data, next);
next += Signatures.getLength(idSizeType);
if (hasNamespace) {
ByteConversion.shortToByte(nsId, data, next);
next += LENGTH_NS_ID;
ByteConversion.shortToByte((short) prefixLen, data, next);
next += LENGTH_PREFIX_LENGTH;
if (nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0)
{UTF8.encode(nodeName.getPrefix(), data, next);}
next += prefixLen;
}
if (prefixData != null)
{System.arraycopy(prefixData, 0, data, next, prefixData.length);}
return data;
} catch (final IOException e) {
return null;
}
}
public static StoredNode deserialize(byte[] data, int start, int len,
DocumentImpl doc, boolean pooled) {
final int end = start + len;
int pos = start;
final byte idSizeType = (byte) (data[pos] & 0x03);
boolean isDirty = (data[pos] & 0x8) == 0x8;
final boolean hasNamespace = (data[pos] & 0x10) == 0x10;
pos += StoredNode.LENGTH_SIGNATURE_LENGTH;
int children = ByteConversion.byteToInt(data, pos);
pos += LENGTH_ELEMENT_CHILD_COUNT;
final int dlnLen = ByteConversion.byteToShort(data, pos);
pos += NodeId.LENGTH_NODE_ID_UNITS;
final NodeId dln = doc.getBrokerPool().getNodeFactory().createFromData(dlnLen, data, pos);
pos += dln.size();
short attributes = ByteConversion.byteToShort(data, pos);
pos += LENGTH_ATTRIBUTES_COUNT;
final short id = (short) Signatures.read(idSizeType, data, pos);
pos += Signatures.getLength(idSizeType);
short nsId = 0;
String prefix = null;
if (hasNamespace) {
nsId = ByteConversion.byteToShort(data, pos);
pos += LENGTH_NS_ID;
int prefixLen = ByteConversion.byteToShort(data, pos);
pos += LENGTH_PREFIX_LENGTH;
if (prefixLen > 0)
{prefix = UTF8.decode(data, pos, prefixLen).toString();}
pos += prefixLen;
}
final String name = doc.getBrokerPool().getSymbols().getName(id);
String namespace = "";
if (nsId != 0)
{namespace = doc.getBrokerPool().getSymbols().getNamespace(nsId);}
ElementImpl node;
if (pooled)
{node = (ElementImpl) NodePool.getInstance().borrowNode(Node.ELEMENT_NODE);}
else
{node = new ElementImpl();}
node.setNodeId(dln);
node.nodeName = doc.getBrokerPool().getSymbols().getQName(Node.ELEMENT_NODE, namespace, name, prefix);
node.children = children;
node.attributes = attributes;
node.isDirty = isDirty;
node.setOwnerDocument(doc);
//TO UNDERSTAND : why is this code here ?
if (end > pos) {
final byte[] pfxData = new byte[end - pos];
System.arraycopy(data, pos, pfxData, 0, end - pos);
final ByteArrayInputStream bin = new ByteArrayInputStream(pfxData);
final DataInputStream in = new DataInputStream(bin);
try {
final short prefixCount = in.readShort();
for (int i = 0; i < prefixCount; i++) {
prefix = in.readUTF();
nsId = in.readShort();
node.addNamespaceMapping(prefix, doc.getBrokerPool().getSymbols().getNamespace(nsId));
}
}
catch (final IOException e) {
e.printStackTrace();
}
}
return node;
}
public static QName readQName(Value value, DocumentImpl document, NodeId nodeId) {
final byte[] data = value.data();
int offset = value.start();
final byte idSizeType = (byte) (data[offset] & 0x03);
final boolean hasNamespace = (data[offset] & 0x10) == 0x10;
offset += StoredNode.LENGTH_SIGNATURE_LENGTH;
offset += LENGTH_ELEMENT_CHILD_COUNT;
offset += NodeId.LENGTH_NODE_ID_UNITS;
offset += nodeId.size();
offset += LENGTH_ATTRIBUTES_COUNT;
final short id = (short) Signatures.read(idSizeType, data, offset);
offset += Signatures.getLength(idSizeType);
short nsId = 0;
String prefix = null;
if (hasNamespace) {
nsId = ByteConversion.byteToShort(data, offset);
offset += LENGTH_NS_ID;
int prefixLen = ByteConversion.byteToShort(data, offset);
offset += LENGTH_PREFIX_LENGTH;
if (prefixLen > 0)
{prefix = UTF8.decode(data, offset, prefixLen).toString();}
offset += prefixLen;
}
final String name = document.getBrokerPool().getSymbols().getName(id);
String namespace = "";
if (nsId != 0)
{namespace = document.getBrokerPool().getSymbols().getNamespace(nsId);}
return new QName(name, namespace, prefix == null ? "" : prefix);
}
public static void readNamespaceDecls(List<String[]> namespaces, Value value, DocumentImpl document, NodeId nodeId) {
final byte[] data = value.data();
int offset = value.start();
final int end = offset + value.getLength();
final byte idSizeType = (byte) (data[offset] & 0x03);
final boolean hasNamespace = (data[offset] & 0x10) == 0x10;
offset += StoredNode.LENGTH_SIGNATURE_LENGTH;
offset += LENGTH_ELEMENT_CHILD_COUNT;
offset += NodeId.LENGTH_NODE_ID_UNITS;
offset += nodeId.size();
offset += LENGTH_ATTRIBUTES_COUNT;
offset += Signatures.getLength(idSizeType);
if (hasNamespace) {
offset += LENGTH_NS_ID;
int prefixLen = ByteConversion.byteToShort(data, offset);
offset += LENGTH_PREFIX_LENGTH;
offset += prefixLen;
}
if (end > offset) {
final byte[] pfxData = new byte[end - offset];
System.arraycopy(data, offset, pfxData, 0, end - offset);
final ByteArrayInputStream bin = new ByteArrayInputStream(pfxData);
final DataInputStream in = new DataInputStream(bin);
try {
final short prefixCount = in.readShort();
String prefix;
short nsId;
for (int i = 0; i < prefixCount; i++) {
prefix = in.readUTF();
nsId = in.readShort();
namespaces.add(new String[] {prefix, document.getBrokerPool().getSymbols().getNamespace(nsId)});
}
}
catch (final IOException e) {
e.printStackTrace();
}
}
}
public void addNamespaceMapping(String prefix, String ns) {
if (prefix == null)
{return;}
if (namespaceMappings == null)
{namespaceMappings = new HashMap<String, String>(1);}
else if (namespaceMappings.containsKey(prefix))
{return;}
namespaceMappings.put(prefix, ns);
}
/**
* Append a child to this node. This method does not rearrange the
* node tree and is only used internally by the parser.
*
* @param child
* @throws DOMException
*/
public void appendChildInternal(StoredNode prevNode, StoredNode child) throws DOMException {
NodeId childId;
if (prevNode == null) {
childId = getNodeId().newChild();
} else {
if (prevNode.getNodeId() == null) {
LOG.warn(getQName() + " : " + prevNode.getNodeName());
}
childId = prevNode.getNodeId().nextSibling();
}
child.setNodeId(childId);
++children;
}
/**
* @see org.w3c.dom.Node#appendChild(org.w3c.dom.Node)
*/
@Override
public Node appendChild(Node child) throws DOMException {
final TransactionManager transact = ownerDocument.getBrokerPool().getTransactionManager();
final Txn transaction = transact.beginTransaction();
final NodeListImpl nl = new NodeListImpl();
nl.add(child);
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
appendChildren(transaction, nl, 0);
broker.storeXMLResource(transaction, (DocumentImpl) getOwnerDocument());
transact.commit(transaction); // bugID 3419602
return getLastChild();
} catch (final Exception e) {
transact.abort(transaction);
throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage());
} finally {
if (broker != null) {
try {
transact.close(transaction);
} finally {
broker.release();
}
}
}
}
public void appendAttributes(Txn transaction, NodeList attribs) throws DOMException {
final NodeList duplicateAttrs = findDupAttributes(attribs);
removeAppendAttributes(transaction, duplicateAttrs, attribs);
}
private NodeList checkForAttributes(Txn transaction, NodeList nodes) throws DOMException {
NodeListImpl attribs = null;
NodeListImpl rest = null;
for (int i = 0; i < nodes.getLength(); i++) {
final Node next = nodes.item(i);
if (next.getNodeType() == Node.ATTRIBUTE_NODE) {
if (!next.getNodeName().startsWith("xmlns")) {
if (attribs == null)
{attribs = new NodeListImpl();}
attribs.add(next);
}
} else if (attribs != null) {
if (rest == null) {rest = new NodeListImpl();}
rest.add(next);
}
}
if (attribs == null)
{return nodes;}
appendAttributes(transaction, attribs);
return rest;
}
@Override
public void appendChildren(Txn transaction, NodeList nodes, int child) throws DOMException {
// attributes are handled differently. Call checkForAttributes to extract them.
nodes = checkForAttributes(transaction, nodes);
if (nodes == null || nodes.getLength() == 0)
{return;}
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final NodePath path = getPath();
StreamListener listener = null;
//May help getReindexRoot() to make some useful things
broker.getIndexController().setDocument(ownerDocument);
StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true, true);
broker.getIndexController().setMode(StreamListener.STORE);
// only reindex if reindexRoot is an ancestor of the current node
if (reindexRoot == null) {
listener = broker.getIndexController().getStreamListener();
}
if (children == 0) {
// no children: append a new child
appendChildren(transaction, nodeId.newChild(), null, new NodeImplRef(this), path, nodes, listener);
} else {
if (child == 1) {
final Node firstChild = getFirstChild();
insertBefore(transaction, nodes, firstChild);
} else {
if (child > 1 && child <= children) {
final NodeList cl = getChildNodes();
final StoredNode last = (StoredNode) cl.item(child - 2);
insertAfter(transaction, nodes, last);
} else {
final StoredNode last = (StoredNode) getLastChild();
appendChildren(transaction, last.getNodeId().nextSibling(), null,
new NodeImplRef(getLastNode(last)), path, nodes, listener);
}
}
}
broker.updateNode(transaction, this, false);
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while appending child node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
}
/**
* Internal append.
*
* @throws DOMException
*/
protected void appendChildren(Txn transaction, NodeId newNodeId, NodeId followingId, NodeImplRef last, NodePath lastPath, NodeList nodes, StreamListener listener) throws DOMException {
if (last == null || last.getNode() == null || last.getNode().getOwnerDocument() == null)
{throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "invalid node");}
children += nodes.getLength();
for (int i = 0; i < nodes.getLength(); i++) {
final Node child = nodes.item(i);
appendChild(transaction, newNodeId, last, lastPath, child, listener);
NodeId next = newNodeId.nextSibling();
if (followingId != null && next.equals(followingId)) {
next = newNodeId.insertNode(followingId);
if (LOG.isDebugEnabled())
{LOG.debug("Node ID collision on " + followingId + ". Using " + next + " instead.");}
}
newNodeId = next;
}
}
private Node appendChild(Txn transaction, NodeId newNodeId, NodeImplRef last, NodePath lastPath, Node child, StreamListener listener)
throws DOMException {
if (last == null || last.getNode() == null)
//TODO : same test as above ? -pb
{throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "invalid node");}
final DocumentImpl owner = (DocumentImpl)getOwnerDocument();
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
switch (child.getNodeType()) {
case Node.DOCUMENT_FRAGMENT_NODE :
appendChildren(transaction, newNodeId, null, last, lastPath,
child.getChildNodes(), listener);
return null; // TODO: implement document fragments so
//we can return all newly appended children
case Node.ELEMENT_NODE :
// create new element
final ElementImpl elem =
new ElementImpl(
new QName(child.getLocalName() == null ?
child.getNodeName() : child.getLocalName(),
child.getNamespaceURI(),
child.getPrefix()),
broker.getBrokerPool().getSymbols()
);
elem.setNodeId(newNodeId);
elem.setOwnerDocument(owner);
final NodeListImpl ch = new NodeListImpl();
final NamedNodeMap attribs = child.getAttributes();
int numActualAttribs = 0;
for (int i = 0; i < attribs.getLength(); i++) {
final Attr attr = (Attr) attribs.item(i);
if (!attr.getNodeName().startsWith("xmlns")) {
ch.add(attr);
numActualAttribs++;
} else {
final String xmlnsDecl = attr.getNodeName();
final String prefix = xmlnsDecl.length() == 5 ? "" : xmlnsDecl.substring(6);
elem.addNamespaceMapping(prefix,attr.getNodeValue());
}
}
final NodeList cl = child.getChildNodes();
for (int i = 0; i < cl.getLength(); i++) {
final Node n = cl.item(i);
if (n.getNodeType() != Node.ATTRIBUTE_NODE)
{ch.add(n);}
}
elem.setChildCount(ch.getLength());
if (numActualAttribs != (short) numActualAttribs)
{throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "Too many attributes");}
elem.setAttributes((short) numActualAttribs);
lastPath.addComponent(elem.getQName());
// insert the node
broker.insertNodeAfter(transaction, last.getNode(), elem);
broker.indexNode(transaction, elem, lastPath);
broker.getIndexController().indexNode(transaction, elem, lastPath, listener);
elem.setChildCount(0);
last.setNode(elem);
//process child nodes
elem.appendChildren(transaction, newNodeId.newChild(), null, last, lastPath, ch, listener);
broker.endElement(elem, lastPath, null);
broker.getIndexController().endElement(transaction, elem, lastPath, listener);
lastPath.removeLastComponent();
return elem;
case Node.TEXT_NODE :
final TextImpl text = new TextImpl(newNodeId, ((Text) child).getData());
text.setOwnerDocument(owner);
// insert the node
broker.insertNodeAfter(transaction, last.getNode(), text);
broker.indexNode(transaction, text, lastPath);
broker.getIndexController().indexNode(transaction, text, lastPath, listener);
last.setNode(text);
return text;
case Node.CDATA_SECTION_NODE :
final CDATASectionImpl cdata = new CDATASectionImpl(newNodeId, ((CDATASection) child).getData());
cdata.setOwnerDocument(owner);
// insert the node
broker.insertNodeAfter(transaction, last.getNode(), cdata);
broker.indexNode(transaction, cdata, lastPath);
last.setNode(cdata);
return cdata;
case Node.ATTRIBUTE_NODE:
final Attr attr = (Attr) child;
final String ns = attr.getNamespaceURI();
final String prefix = (Namespaces.XML_NS.equals(ns) ? "xml" : attr.getPrefix());
String name = attr.getLocalName();
if (name == null) {name = attr.getName();}
final QName attrName = new QName(name, ns, prefix);
final AttrImpl attrib = new AttrImpl(attrName, attr.getValue(), broker.getBrokerPool().getSymbols());
attrib.setNodeId(newNodeId);
attrib.setOwnerDocument(owner);
if (ns != null && attrName.compareTo(Namespaces.XML_ID_QNAME) == Constants.EQUAL) {
// an xml:id attribute. Normalize the attribute and set its type to ID
attrib.setValue(StringValue.trimWhitespace(StringValue.collapseWhitespace(attrib.getValue())));
attrib.setType(AttrImpl.ID);
} else {
attrName.setNameType(ElementValue.ATTRIBUTE);
}
broker.insertNodeAfter(transaction, last.getNode(), attrib);
broker.indexNode(transaction, attrib, lastPath);
broker.getIndexController().indexNode(transaction, attrib, lastPath, listener);
last.setNode(attrib);
return attrib;
case Node.COMMENT_NODE:
final CommentImpl comment = new CommentImpl(((Comment) child).getData());
comment.setNodeId(newNodeId);
comment.setOwnerDocument(owner);
// insert the node
broker.insertNodeAfter(transaction, last.getNode(), comment);
broker.indexNode(transaction, comment, lastPath);
last.setNode(comment);
return comment;
case Node.PROCESSING_INSTRUCTION_NODE:
final ProcessingInstructionImpl pi =
new ProcessingInstructionImpl(newNodeId,
((ProcessingInstruction) child).getTarget(),
((ProcessingInstruction) child).getData());
pi.setOwnerDocument(owner);
//insert the node
broker.insertNodeAfter(transaction, last.getNode(), pi);
broker.indexNode(transaction, pi, lastPath);
last.setNode(pi);
return pi;
default :
throw new DOMException(DOMException.INVALID_MODIFICATION_ERR,
"Unknown node type: " +
child.getNodeType() + " " + child.getNodeName());
}
} catch (final EXistException e) {
LOG.warn("Exception while appending node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
return null;
}
@Override
public short getAttributesCount() {
return attributes;
}
/**
* Set the attributes that belong to this node.
*
* @param attribNum The new attributes value
*/
@Override
public void setAttributes(short attribNum) {
attributes = attribNum;
}
/**
* @see org.w3c.dom.Element#getAttribute(java.lang.String)
*/
public String getAttribute(String name) {
final Attr attr = findAttribute(name);
return attr != null ? attr.getValue() : "";
}
/**
* @see org.w3c.dom.Element#getAttributeNS(java.lang.String, java.lang.String)
*/
public String getAttributeNS(String namespaceURI, String localName) {
final Attr attr = findAttribute(new QName(localName, namespaceURI));
return attr != null ? attr.getValue() : "";
//XXX: if not present must return null
}
@Deprecated //move as soon as getAttributeNS null issue resolved
public String _getAttributeNS(String namespaceURI, String localName) {
final Attr attr = findAttribute(new QName(localName, namespaceURI));
return attr != null ? attr.getValue() : null;
}
/**
* @see org.w3c.dom.Element#getAttributeNode(java.lang.String)
*/
public Attr getAttributeNode(String name) {
return findAttribute(name);
}
/**
* @see org.w3c.dom.Element#getAttributeNodeNS(java.lang.String, java.lang.String)
*/
public Attr getAttributeNodeNS(String namespaceURI, String localName) {
return findAttribute(new QName(localName, namespaceURI));
}
/**
* @see org.w3c.dom.Node#getAttributes()
*/
@Override
public NamedNodeMap getAttributes() {
final NamedNodeMapImpl map = new NamedNodeMapImpl();
if (getAttributesCount() > 0) {
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final Iterator<StoredNode> iterator = broker.getNodeIterator(this);
iterator.next();
final int ccount = getChildCount();
for (int i = 0; i < ccount; i++) {
final StoredNode next = iterator.next();
if (next.getNodeType() != Node.ATTRIBUTE_NODE)
{break;}
map.setNamedItem(next);
}
} catch (final EXistException e) {
LOG.warn("Exception while retrieving attributes: " + e.getMessage());
} finally {
if (broker != null)
broker.release();
}
}
if (declaresNamespacePrefixes()) {
for (final Iterator<Map.Entry<String, String>> i =
namespaceMappings.entrySet().iterator(); i.hasNext(); ) {
final Map.Entry<String, String> entry = i.next();
final String prefix = entry.getKey().toString();
final String ns = entry.getValue().toString();
final QName attrName = new QName(prefix, Namespaces.XMLNS_NS, "xmlns");
final AttrImpl attr = new AttrImpl(attrName, ns, null);
attr.setOwnerDocument(ownerDocument);
map.setNamedItem(attr);
}
}
return map;
}
private AttrImpl findAttribute(String qname) {
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final Iterator<StoredNode> iterator = broker.getNodeIterator(this);
iterator.next();
return findAttribute(qname, iterator, this);
} catch (final EXistException e) {
LOG.warn("Exception while retrieving attributes: " + e.getMessage());
} finally {
if (broker != null)
broker.release();
}
return null;
}
private AttrImpl findAttribute(String qname, Iterator<StoredNode> iterator, StoredNode current) {
final int ccount = current.getChildCount();
StoredNode next;
for (int i = 0; i < ccount; i++) {
next = iterator.next();
if (next.getNodeType() != Node.ATTRIBUTE_NODE)
{break;}
if (next.getNodeName().equals(qname))
{return (AttrImpl) next;}
}
return null;
}
private AttrImpl findAttribute(QName qname) {
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final Iterator<StoredNode> iterator = broker.getNodeIterator(this);
iterator.next();
return findAttribute(qname, iterator, this);
} catch (final EXistException e) {
LOG.warn("Exception while retrieving attributes: " + e.getMessage());
} finally {
if (broker != null)
broker.release();
}
return null;
}
private AttrImpl findAttribute(QName qname, Iterator<StoredNode> iterator, StoredNode current) {
final int ccount = current.getChildCount();
for (int i = 0; i < ccount; i++) {
final StoredNode next = iterator.next();
if (next.getNodeType() != Node.ATTRIBUTE_NODE)
{break;}
if (next.getQName().equalsSimple(qname))
{return (AttrImpl) next;}
}
return null;
}
/**
* Returns a list of all attribute nodes in attrs that are already present
* in the current element.
*
* @param attrs
* @return The attributes list
* @throws DOMException
*/
private NodeList findDupAttributes(NodeList attrs) throws DOMException {
NodeListImpl dupList = null;
final NamedNodeMap map = getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
final Node attr = attrs.item(i);
//Workaround: Xerces sometimes returns null for getLocalName() !!!!
String localName = attr.getLocalName();
if (localName == null)
{localName = attr.getNodeName();}
final Node duplicate = map.getNamedItemNS(attr.getNamespaceURI(), localName);
if (duplicate != null) {
if (dupList == null)
{dupList = new NodeListImpl();}
dupList.add(duplicate);
}
}
return dupList;
}
/**
* @see org.exist.dom.NodeImpl#getChildCount()
*/
@Override
public int getChildCount() {
return children;
}
@Override
public NodeList getChildNodes() {
final NodeListImpl childList = new NodeListImpl(1);
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
for (final EmbeddedXMLStreamReader reader = broker.getXMLStreamReader(this, true);
reader.hasNext();) {
final int status = reader.next();
if (status != XMLStreamConstants.END_ELEMENT) {
if (((NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID)).isChildOf(nodeId))
{childList.add(reader.getNode());}
}
}
} catch (final IOException e) {
LOG.warn("Internal error while reading child nodes: " + e.getMessage(), e);
} catch (final XMLStreamException e) {
LOG.warn("Internal error while reading child nodes: " + e.getMessage(), e);
} catch (final EXistException e) {
LOG.warn("Internal error while reading child nodes: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
return childList;
}
/**
* @see org.w3c.dom.Element#getElementsByTagName(java.lang.String)
*/
public NodeList getElementsByTagName(String tagName) {
final QName qname = new QName(tagName, "", null);
return ((DocumentImpl)getOwnerDocument()).findElementsByTagName(this, qname);
}
/**
* @see org.w3c.dom.Element#getElementsByTagNameNS(java.lang.String, java.lang.String)
*/
public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
final QName qname = new QName(localName, namespaceURI, null);
return ((DocumentImpl)getOwnerDocument()).findElementsByTagName(this, qname);
}
/**
* @see org.w3c.dom.Node#getFirstChild()
*/
@Override
public Node getFirstChild() {
if (!hasChildNodes() || getChildCount() == getAttributesCount())
{return null;}
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final Iterator<StoredNode> iterator = broker.getNodeIterator(this);
iterator.next();
StoredNode next;
for (int i = 0; i < getChildCount(); i++) {
next = iterator.next();
if (next.getNodeType() != Node.ATTRIBUTE_NODE)
{return next;}
}
} catch (final EXistException e) {
LOG.warn("Exception while retrieving child node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
return null;
}
@Override
public Node getLastChild() {
if (!hasChildNodes())
{return null;}
Node node = null;
if (!isDirty) {
final NodeId child = nodeId.getChild(children);
node = ownerDocument.getNode(new NodeProxy(ownerDocument, child));
}
if (node == null) {
final NodeList cl = getChildNodes();
return cl.item(cl.getLength() - 1);
}
return node;
}
/**
* @see org.w3c.dom.Element#getTagName()
*/
public String getTagName() {
return nodeName.getStringValue();
}
/**
* @see org.w3c.dom.Element#hasAttribute(java.lang.String)
*/
public boolean hasAttribute(String name) {
return findAttribute(name) != null;
}
/**
* @see org.w3c.dom.Element#hasAttributeNS(java.lang.String, java.lang.String)
*/
public boolean hasAttributeNS(String namespaceURI, String localName) {
return findAttribute(new QName(localName, namespaceURI)) != null;
}
/**
* @see org.w3c.dom.Node#hasAttributes()
*/
@Override
public boolean hasAttributes() {
return (getAttributesCount() > 0);
}
/**
* @see org.w3c.dom.Node#hasChildNodes()
*/
@Override
public boolean hasChildNodes() {
return children > 0;
}
/**
* @see org.w3c.dom.Node#getNodeValue()
*/
//TODO getNodeValue() on org.exist.dom.ElementImpl should return null according to W3C spec, and getTextContent() should be implemented!
@Override
public String getNodeValue() /*throws DOMException*/ {
//TODO : parametrize the boolean value ?
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
return broker.getNodeValue(this, false);
} catch (final EXistException e) {
LOG.warn("Exception while reading node value: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
return "";
}
/**
* @see org.w3c.dom.Element#removeAttribute(java.lang.String)
*/
public void removeAttribute(String name) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"removeAttribute(String name) not implemented on class " + getClass().getName());
}
/**
* @see org.w3c.dom.Element#removeAttributeNS(java.lang.String, java.lang.String)
*/
public void removeAttributeNS(String namespaceURI, String name) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"removeAttributeNS(String namespaceURI, String name) not implemented on class " + getClass().getName());
}
public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"removeAttributeNode(Attr oldAttr) not implemented on class " + getClass().getName());
}
public void setAttribute(String name, String value) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setAttribute(String name, String value) not implemented on class " + getClass().getName());
}
public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setAttributeNS(String namespaceURI, String qualifiedName," +
"String value) not implemented on class " + getClass().getName());
}
public Attr setAttributeNode(Attr newAttr) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setAttributeNode(Attr newAttr) not implemented on class " + getClass().getName());
}
public Attr setAttributeNodeNS(Attr newAttr) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setAttributeNodeNS(Attr newAttr) not implemented on class " + getClass().getName());
}
@Override
public void setChildCount(int count) {
children = count;
}
public void setNamespaceMappings(Map<String, String> map) {
namespaceMappings = new HashMap<String, String>(map);
for (final String ns : namespaceMappings.values()) {
ownerDocument.getBrokerPool().getSymbols().getNSSymbol(ns);
}
}
public Map<String, String> getNamespaceMap() {
return new HashMap<String, String>(namespaceMappings);
}
public Iterator<String> getPrefixes() {
return namespaceMappings.keySet().iterator();
}
public String getNamespaceForPrefix(String prefix) {
return namespaceMappings.get(prefix);
}
public int getPrefixCount() {
return namespaceMappings.size();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toString(true);
}
/**
*/
@Override
public String toString(boolean top) {
return toString(top, new TreeSet<String>());
}
/**
* Method toString.
*
*/
public String toString(boolean top, TreeSet<String> namespaces) {
final StringBuilder buf = new StringBuilder();
final StringBuilder attributes = new StringBuilder();
final StringBuilder children = new StringBuilder();
buf.append('<');
buf.append(nodeName);
//Remove false to have a verbose output
//if (top && false) {
//buf.append(" xmlns:exist=\""+ Namespaces.EXIST_NS + "\"");
//buf.append(" exist:id=\"");
//buf.append(getNodeId());
//buf.append("\" exist:document=\"");
//buf.append(((DocumentImpl)getOwnerDocument()).getFileURI());
//buf.append("\"");
//}
if (declaresNamespacePrefixes()) {
// declare namespaces used by this element
Map.Entry<String, String> entry;
String namespace, prefix;
for (final Iterator<Map.Entry<String, String>>
i = namespaceMappings.entrySet().iterator(); i.hasNext();) {
entry = i.next();
prefix = entry.getKey();
namespace = entry.getValue();
if (prefix.length() == 0) {
buf.append(" xmlns=\"");
//buf.append(namespace);
buf.append("...");
}
else {
buf.append(" xmlns:");
buf.append(prefix);
buf.append("=\"");
//buf.append(namespace);
buf.append("...");
}
buf.append("\" ");
namespaces.add(namespace);
}
}
if (nodeName.getNamespaceURI().length() > 0
&& (!namespaces.contains(nodeName.getNamespaceURI()))) {
buf.append(" xmlns:").append(nodeName.getPrefix()).append("=\"");
buf.append(nodeName.getNamespaceURI());
buf.append("\" ");
}
final NodeList childNodes = getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
final Node child = childNodes.item(i);
switch (child.getNodeType()) {
case Node.ATTRIBUTE_NODE:
attributes.append(' ');
attributes.append(((Attr) child).getName());
attributes.append("=\"");
attributes.append(escapeXml(child));
attributes.append("\"");
break;
case Node.ELEMENT_NODE:
children.append(((ElementImpl) child).toString(false, namespaces));
break;
default :
children.append(child.toString());
}
}
if (attributes.length() > 0)
{buf.append(attributes.toString());}
if (childNodes.getLength() > 0) {
buf.append(">");
buf.append(children.toString());
buf.append("</");
buf.append(nodeName);
buf.append(">");
}
else
{buf.append("/>");}
return buf.toString();
}
/**
* @see org.w3c.dom.Node#insertBefore(org.w3c.dom.Node, org.w3c.dom.Node)
*/
@Override
public Node insertBefore(Node newChild, Node refChild) throws DOMException {
if (refChild == null)
{return appendChild(newChild);}
if (!(refChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Wrong node type");}
final NodeListImpl nl = new NodeListImpl();
nl.add(newChild);
final TransactionManager transact = ownerDocument.getBrokerPool().getTransactionManager();
final Txn transaction = transact.beginTransaction();
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
insertBefore(transaction, nl, refChild);
broker.storeXMLResource(transaction, (DocumentImpl) getOwnerDocument());
transact.commit(transaction);
return refChild.getPreviousSibling();
} catch(final TransactionException e) {
transact.abort(transaction);
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, e.getMessage());
} catch (final EXistException e) {
transact.abort(transaction);
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null) {
try {
transact.close(transaction);
} finally {
broker.release();
}
}
}
return null;
}
/**
* Insert a list of nodes at the position before the reference
* child.
*/
@Override
public void insertBefore(Txn transaction, NodeList nodes, Node refChild) throws DOMException {
if (refChild == null) {
//TODO : use NodeImpl.UNKNOWN_NODE_IMPL_GID ? -pb
appendChildren(transaction, nodes, -1);
return;
}
if (!(refChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type");}
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final NodePath path = getPath();
StreamListener listener = null;
//May help getReindexRoot() to make some useful things
broker.getIndexController().setDocument(ownerDocument);
final StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true, true);
broker.getIndexController().setMode(StreamListener.STORE);
if (reindexRoot == null) {
listener = broker.getIndexController().getStreamListener();
}
final StoredNode following = (StoredNode) refChild;
final StoredNode previous = (StoredNode) following.getPreviousSibling();
if (previous == null) {
// there's no sibling node before the new node
final NodeId newId = following.getNodeId().insertBefore();
appendChildren(transaction, newId, following.getNodeId(), new NodeImplRef(this),
path, nodes, listener);
} else {
// insert the new node between the preceding and following sibling
final NodeId newId = previous.getNodeId().insertNode(following.getNodeId());
appendChildren(transaction, newId, following.getNodeId(),
new NodeImplRef(getLastNode(previous)), path, nodes, listener);
}
setDirty(true);
broker.updateNode(transaction, this, true);
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
}
/**
* Insert a list of nodes at the position following the reference
* child.
*/
@Override
public void insertAfter(Txn transaction, NodeList nodes, Node refChild) throws DOMException {
if (refChild == null) {
//TODO : use NodeImpl.UNKNOWN_NODE_IMPL_GID ? -pb
appendChildren(null, nodes, -1);
return;
}
if (!(refChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type: ");}
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
final NodePath path = getPath();
StreamListener listener = null;
//May help getReindexRoot() to make some useful things
broker.getIndexController().setDocument(ownerDocument);
final StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true, true);
broker.getIndexController().setMode(StreamListener.STORE);
if (reindexRoot == null) {
listener = broker.getIndexController().getStreamListener();
}
final StoredNode previous = (StoredNode) refChild;
final StoredNode following = (StoredNode) previous.getNextSibling();
final NodeId followingId = following == null ? null : following.getNodeId();
final NodeId newNodeId = previous.getNodeId().insertNode(followingId);
appendChildren(transaction, newNodeId, followingId, new NodeImplRef(getLastNode(previous)), path, nodes, listener);
setDirty(true);
broker.updateNode(transaction, this, true);
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
}
/**
* Update the contents of this element. The passed list of nodes
* becomes the new content.
*
* @param newContent
* @throws DOMException
*/
public void update(Txn transaction, NodeList newContent) throws DOMException {
final NodePath path = getPath();
// remove old child nodes
final NodeList nodes = getChildNodes();
StreamListener listener = null;
DBBroker broker = null;
//May help getReindexRoot() to make some useful things
try {
broker = ownerDocument.getBrokerPool().get(null);
broker.getIndexController().setDocument(ownerDocument);
final StoredNode reindexRoot = broker.getIndexController().getReindexRoot(this, path, true, true);
broker.getIndexController().setMode(StreamListener.REMOVE_SOME_NODES);
if (reindexRoot == null) {
listener = broker.getIndexController().getStreamListener();
} else {
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES);
}
// TODO: fix once range index has been moved to new architecture
final StoredNode valueReindexRoot = broker.getValueIndex().getReindexRoot(this, path);
broker.getValueIndex().reindex(valueReindexRoot);
StoredNode last = this;
int i = nodes.getLength();
for (; i > 0; i--) {
StoredNode child = (StoredNode) nodes.item(i - 1);
if (child.getNodeType() == Node.ATTRIBUTE_NODE) {
last = child;
break;
}
if (child.getNodeType() == Node.ELEMENT_NODE)
{path.addComponent(child.getQName());}
broker.removeAllNodes(transaction, child, path, listener);
if (child.getNodeType() == Node.ELEMENT_NODE)
{path.removeLastComponent();}
}
broker.getIndexController().flush();
broker.getIndexController().setMode(StreamListener.STORE);
broker.getIndexController().getStreamListener();
broker.endRemove(transaction);
children = i;
final NodeId newNodeId = last == this ? nodeId.newChild() : last.nodeId.nextSibling();
//Append new content
appendChildren(transaction, newNodeId, null, new NodeImplRef(last), path, newContent, listener);
broker.updateNode(transaction, this, false);
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);
broker.getValueIndex().reindex(valueReindexRoot);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
}
/**
* Update a child node. This method will only update the child node
* but not its potential descendant nodes.
*
* @param oldChild
* @param newChild
* @throws DOMException
*/
@Override
public StoredNode updateChild(Txn transaction, Node oldChild, Node newChild) throws DOMException {
if (!(oldChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Wrong node type");}
if (!(newChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Wrong node type");}
StoredNode oldNode = (StoredNode) oldChild;
final StoredNode newNode = (StoredNode) newChild;
if (!oldNode.nodeId.getParentId().equals(nodeId))
{throw new DOMException(DOMException.NOT_FOUND_ERR,
"Node is not a child of this element");}
if (newNode.getNodeType() == Node.ATTRIBUTE_NODE) {
if (newNode.getQName().equalsSimple(Namespaces.XML_ID_QNAME)) {
// an xml:id attribute. Normalize the attribute and set its type to ID
final AttrImpl attr = (AttrImpl) newNode;
attr.setValue(StringValue.trimWhitespace(StringValue.collapseWhitespace(attr.getValue())));
attr.setType(AttrImpl.ID);
}
}
StoredNode previousNode = (StoredNode) oldNode.getPreviousSibling();
if (previousNode == null)
{previousNode = this;}
else
{previousNode = getLastNode(previousNode);}
final NodePath currentPath = getPath();
final NodePath oldPath = oldNode.getPath(currentPath);
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
//May help getReindexRoot() to make some useful things
broker.getIndexController().setDocument(ownerDocument);
//Check if the change affects any ancestor nodes, which then need to be reindexed later
StoredNode reindexRoot = broker.getIndexController().getReindexRoot(oldNode, oldPath, false);
//Remove indexes
if (reindexRoot == null)
{reindexRoot = oldNode;}
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES);
//TODO: fix once range index has been moved to new architecture
final StoredNode valueReindexRoot = broker.getValueIndex().getReindexRoot(this, oldPath);
broker.getValueIndex().reindex(valueReindexRoot);
//Remove the actual node data
broker.removeNode(transaction, oldNode, oldPath, null);
broker.endRemove(transaction);
newNode.nodeId = oldNode.nodeId;
//Reinsert the new node data
broker.insertNodeAfter(transaction, previousNode, newNode);
final NodePath path = newNode.getPath(currentPath);
broker.indexNode(transaction, newNode, path);
if (newNode.getNodeType() == Node.ELEMENT_NODE)
{broker.endElement(newNode, path, null);}
broker.updateNode(transaction, this, true);
//Recreate indexes on ancestor nodes
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);
broker.getValueIndex().reindex(valueReindexRoot);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
return newNode;
}
/**
* @see org.w3c.dom.Node#removeChild(org.w3c.dom.Node)
*/
@Override
public Node removeChild(Txn transaction, Node oldChild) throws DOMException {
if (!(oldChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "wrong node type");}
final StoredNode oldNode = (StoredNode) oldChild;
if (!oldNode.nodeId.getParentId().equals(nodeId))
{throw new DOMException(DOMException.NOT_FOUND_ERR,
"node is not a child of this element");}
final NodePath oldPath = oldNode.getPath();
StreamListener listener = null;
DBBroker broker = null;
try {
//May help getReindexRoot() to make some useful things
broker = ownerDocument.getBrokerPool().get(null);
broker.getIndexController().setDocument(ownerDocument);
final StoredNode reindexRoot = broker.getIndexController().getReindexRoot(oldNode, oldPath, false);
broker.getIndexController().setMode(StreamListener.REMOVE_SOME_NODES);
if (reindexRoot == null) {
listener = broker.getIndexController().getStreamListener();
} else {
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.REMOVE_SOME_NODES);
}
broker.removeAllNodes(transaction, oldNode, oldPath, listener);
--children;
if (oldChild.getNodeType() == Node.ATTRIBUTE_NODE)
{--attributes;}
broker.endRemove(transaction);
setDirty(true);
broker.updateNode(transaction, this, false);
broker.flush();
if (reindexRoot != null && !reindexRoot.getNodeId().equals(oldNode.getNodeId()))
{broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);}
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
return oldNode;
}
public void removeAppendAttributes(Txn transaction, NodeList removeList, NodeList appendList) {
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
if (removeList != null) {
try {
for (int i=0; i<removeList.getLength(); i++) {
final Node oldChild = removeList.item(i);
if (!(oldChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Wrong node type");}
final StoredNode old = (StoredNode) oldChild;
if (!old.nodeId.isChildOf(nodeId))
{throw new DOMException(DOMException.NOT_FOUND_ERR, "node " +
old.nodeId.getParentId() +
" is not a child of element " + nodeId);}
final NodePath oldPath = old.getPath();
// remove old custom indexes
broker.getIndexController().reindex(transaction, old,
StreamListener.REMOVE_SOME_NODES);
broker.removeNode(transaction, old, oldPath, null);
children--;
attributes--;
}
} finally {
broker.endRemove(transaction);
}
}
final NodePath path = getPath();
broker.getIndexController().setDocument(ownerDocument, StreamListener.STORE);
final StreamListener listener = broker.getIndexController().getStreamListener();
if (children == 0) {
appendChildren(transaction, nodeId.newChild(), null,
new NodeImplRef(this), path, appendList, listener);
} else {
if (attributes == 0) {
final StoredNode firstChild = (StoredNode) getFirstChild();
final NodeId newNodeId = firstChild.nodeId.insertBefore();
appendChildren(transaction, newNodeId, firstChild.getNodeId(),
new NodeImplRef(this), path, appendList, listener);
} else {
final AttribVisitor visitor = new AttribVisitor();
accept(visitor);
final NodeId firstChildId = visitor.firstChild == null ? null : visitor.firstChild.nodeId;
final NodeId newNodeId = visitor.lastAttrib.nodeId.insertNode(firstChildId);
appendChildren(transaction, newNodeId, firstChildId, new NodeImplRef(visitor.lastAttrib),
path, appendList, listener);
}
setDirty(true);
}
attributes += appendList.getLength();
broker.updateNode(transaction, this, true);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
}
@Deprecated
private class AttribVisitor implements NodeVisitor {
private StoredNode lastAttrib = null;
private StoredNode firstChild = null;
public boolean visit(StoredNode node) {
if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
lastAttrib = node;
} else if (node.nodeId.isChildOf(ElementImpl.this.nodeId)) {
firstChild = node;
return false;
}
return true;
}
}
/**
* Replaces the oldNode with the newChild
*
* @param transaction
* @param newChild
* @param oldChild
*
* @return The new node (this differs from the {@link org.w3c.dom.Node#replaceChild(Node, Node)} specification)
*
* @see org.w3c.dom.Node#replaceChild(org.w3c.dom.Node, org.w3c.dom.Node)
*/
@Override
public Node replaceChild(Txn transaction, Node newChild, Node oldChild) throws DOMException {
if (!(oldChild instanceof StoredNode))
{throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Wrong node type");}
final StoredNode oldNode = (StoredNode) oldChild;
if (!oldNode.nodeId.getParentId().equals(nodeId))
{throw new DOMException(DOMException.NOT_FOUND_ERR,
"Node is not a child of this element");}
StoredNode previous = (StoredNode) oldNode.getPreviousSibling();
if (previous == null)
{previous = this;}
else
{previous = getLastNode(previous);}
final NodePath oldPath = oldNode.getPath();
StreamListener listener = null;
//May help getReindexRoot() to make some useful things
Node newNode = null;
DBBroker broker = null;
try {
broker = ownerDocument.getBrokerPool().get(null);
broker.getIndexController().setDocument(ownerDocument);
final StoredNode reindexRoot = broker.getIndexController().getReindexRoot(oldNode, oldPath, false);
broker.getIndexController().setMode(StreamListener.REMOVE_SOME_NODES);
if (reindexRoot == null) {
listener = broker.getIndexController().getStreamListener();
} else {
broker.getIndexController().reindex(transaction, reindexRoot,
StreamListener.REMOVE_SOME_NODES);
}
broker.removeAllNodes(transaction, oldNode, oldPath, listener);
broker.endRemove(transaction);
broker.flush();
broker.getIndexController().setMode(StreamListener.STORE);
listener = broker.getIndexController().getStreamListener();
newNode = appendChild(transaction, oldNode.nodeId, new NodeImplRef(previous),
getPath(), newChild, listener);
//Reindex if required
final DocumentImpl owner = (DocumentImpl)getOwnerDocument();
broker.storeXMLResource(transaction, owner);
broker.updateNode(transaction, this, false);
broker.getIndexController().reindex(transaction, reindexRoot, StreamListener.STORE);
broker.flush();
} catch (final EXistException e) {
LOG.warn("Exception while inserting node: " + e.getMessage(), e);
} finally {
if (broker != null)
broker.release();
}
//return oldChild; // method is spec'd to return the old child, even though that's probably useless in this case
return newNode; //returning the newNode is more sensible than returning the oldNode
}
private String escapeXml(Node child) {
final String str = ((Attr) child).getValue();
StringBuilder buffer = null;
String entity = null;
char ch;
for (int i = 0; i < str.length(); i++) {
ch = str.charAt(i);
switch (ch) {
case '"':
entity = """;
break;
case '<':
entity = "<";
break;
case '>':
entity = ">";
break;
case '\'':
entity = "'";
break;
default :
entity = null;
break;
}
if (buffer == null) {
if (entity != null) {
buffer = new StringBuilder(str.length() + 20);
buffer.append(str.substring(0, i));
buffer.append(entity);
}
} else {
if (entity == null)
{buffer.append(ch);}
else
{buffer.append(entity);}
}
}
return (buffer == null) ? str : buffer.toString();
}
public void setPreserveSpace(boolean preserveWS) {
this.preserveWS = preserveWS;
}
public boolean preserveSpace() {
return preserveWS;
}
/** ? @see org.w3c.dom.Element#getSchemaTypeInfo()
*/
public TypeInfo getSchemaTypeInfo() {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"getSchemaTypeInfo() not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Element#setIdAttribute(java.lang.String, boolean)
*/
public void setIdAttribute(String name, boolean isId) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setIdAttribute(String name, boolean isId) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Element#setIdAttributeNS(java.lang.String, java.lang.String, boolean)
*/
public void setIdAttributeNS(String namespaceURI, String localName, boolean isId) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setIdAttributeNS(String namespaceURI, String localName," +
" boolean isId) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Element#setIdAttributeNode(org.w3c.dom.Attr, boolean)
*/
public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setIdAttributeNode(Attr idAttr, boolean isId) not implemented on class " + getClass().getName());
}
@Override
public String getBaseURI() {
final XmldbURI baseURI = calculateBaseURI();
if (baseURI != null)
{return baseURI.toString();}
return ""; //UNDERSTAND: is it ok?
}
//Please, keep in sync with org.exist.memtree.ElementImpl
protected XmldbURI calculateBaseURI() {
XmldbURI baseURI = null;
final String nodeBaseURI = _getAttributeNS(Namespaces.XML_NS, "base");
if (nodeBaseURI != null) {
baseURI = XmldbURI.create(nodeBaseURI, false);
if (baseURI.isAbsolute())
{return baseURI;}
}
final StoredNode parent = getParentStoredNode();
if (parent != null) {
if (nodeBaseURI == null) {
baseURI = parent.calculateBaseURI();
} else {
XmldbURI parentsBaseURI = parent.calculateBaseURI();
if (nodeBaseURI.isEmpty())
{baseURI = parentsBaseURI;}
else {
baseURI = parentsBaseURI.append(baseURI);
}
}
} else {
if (nodeBaseURI == null)
{return XmldbURI.create(getDocument().getBaseURI(), false);}
else {
final String docBaseURI = getDocument().getBaseURI();
if (docBaseURI.endsWith("/")) {
baseURI = XmldbURI.create(getDocument().getBaseURI(), false);
baseURI.append(baseURI);
} else {
baseURI = XmldbURI.create(getDocument().getBaseURI(), false);
baseURI = baseURI.removeLastSegment();
baseURI.append(baseURI);
}
}
}
return baseURI;
}
/** ? @see org.w3c.dom.Node#compareDocumentPosition(org.w3c.dom.Node)
*/
@Override
public short compareDocumentPosition(Node other) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"compareDocumentPosition(Node other) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#getTextContent()
*/
@Override
public String getTextContent() throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"getTextContent() not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#setTextContent(java.lang.String)
*/
@Override
public void setTextContent(String textContent) throws DOMException {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setTextContent(String textContent) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#isSameNode(org.w3c.dom.Node)
*/
@Override
public boolean isSameNode(Node other) {
// This function is used by Saxon in some circumstances, and this partial implementation is required for proper Saxon operation.
if (other instanceof StoredNode) {
return (this.nodeId == ((StoredNode)other).nodeId &&
this.ownerDocument.getDocId() == ((StoredNode)other).ownerDocument.getDocId());
}
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"isSameNode(Node other) not implemented on other class " + other.getClass().getName());
}
/** ? @see org.w3c.dom.Node#lookupPrefix(java.lang.String)
*/
@Override
public String lookupPrefix(String namespaceURI) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"lookupPrefix(String namespaceURI) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#isDefaultNamespace(java.lang.String)
*/
@Override
public boolean isDefaultNamespace(String namespaceURI) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"isDefaultNamespace(String namespaceURI) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#lookupNamespaceURI(java.lang.String)
*/
@Override
public String lookupNamespaceURI(String prefix) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"lookupNamespaceURI(String prefix) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#isEqualNode(org.w3c.dom.Node)
*/
@Override
public boolean isEqualNode(Node arg) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"isEqualNode(Node arg) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#getFeature(java.lang.String, java.lang.String)
*/
@Override
public Object getFeature(String feature, String version) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"getFeature(String feature, String version) not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#setUserData(java.lang.String, java.lang.Object, org.w3c.dom.UserDataHandler)
*/
@Override
public Object setUserData(String key, Object data, UserDataHandler handler) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"setUserData(String key, Object data, UserDataHandler handler) " +
"not implemented on class " + getClass().getName());
}
/** ? @see org.w3c.dom.Node#getUserData(java.lang.String)
*/
@Override
public Object getUserData(String key) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"getUserData(String key) not implemented on class " + getClass().getName());
}
@Override
public boolean accept(Iterator<StoredNode> iterator, NodeVisitor visitor) {
if (!visitor.visit(this))
{return false;}
if (hasChildNodes()) {
final int ccount = getChildCount();
StoredNode next;
for (int i = 0; i < ccount; i++) {
next = iterator.next();
if (!next.accept(iterator, visitor))
{return false;}
}
}
return true;
}
}