/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 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 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.persistent; import org.exist.EXistException; import org.exist.Resource; import org.exist.dom.QName; import org.exist.collections.Collection; import org.exist.collections.CollectionConfiguration; import org.exist.numbering.NodeId; import org.exist.security.ACLPermission; import org.exist.security.Account; import org.exist.security.Permission; import org.exist.security.PermissionDeniedException; import org.exist.security.PermissionFactory; import org.exist.security.SecurityManager; import org.exist.security.UnixStylePermission; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.storage.ElementValue; import org.exist.storage.NodePath; import org.exist.storage.StorageAddress; import org.exist.storage.io.VariableByteInput; import org.exist.storage.io.VariableByteOutputStream; import org.exist.storage.lock.Lock; import org.exist.storage.lock.MultiReadReentrantLock; import org.exist.storage.txn.Txn; import org.exist.xmldb.XmldbURI; import org.exist.xquery.Constants; import org.exist.xquery.DescendantSelector; import org.exist.xquery.Expression; import org.exist.xquery.NodeSelector; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import javax.xml.XMLConstants; import java.io.EOFException; import java.io.IOException; /** * Represents a persistent document object in the database; * it can be an XML_FILE , a BINARY_FILE, or Xquery source code. * * @author Wolfgang Meier <wolfgang@exist-db.org> */ public class DocumentImpl extends NodeImpl<DocumentImpl> implements Resource, Document { public static final int UNKNOWN_DOCUMENT_ID = -1; public static final byte XML_FILE = 0; public static final byte BINARY_FILE = 1; public static final int LENGTH_DOCUMENT_ID = 4; //sizeof int public static final int LENGTH_DOCUMENT_TYPE = 1; //sizeof byte //public static final byte DOCUMENT_NODE_SIGNATURE = 0x0F; private final BrokerPool pool; /** * number of child nodes */ private int children = 0; private long[] childAddress = null; /** * the collection this document belongs to */ private transient Collection collection = null; /** * the document's id */ private int docId = UNKNOWN_DOCUMENT_ID; /** * the document's file name */ private XmldbURI fileURI = null; private Permission permissions = null; private transient Lock updateLock = null; private DocumentMetadata metadata = null; /** * Creates a new <code>DocumentImpl</code> instance. * * @param pool a <code>BrokerPool</code> instance representing the db */ public DocumentImpl(final BrokerPool pool) { this(pool, null, null); } /** * Creates a new <code>DocumentImpl</code> instance. * * @param pool a <code>BrokerPool</code> instance representing the db * @param collection a <code>Collection</code> value * @param fileURI a <code>XmldbURI</code> value */ public DocumentImpl(final BrokerPool pool, final Collection collection, final XmldbURI fileURI) { this.pool = pool; this.collection = collection; this.fileURI = fileURI; // the permissions assigned to this document this.permissions = PermissionFactory.getDefaultResourcePermission(pool.getSecurityManager()); //inherit the group to the resource if current collection is setGid if(collection != null && collection.getPermissions().isSetGid()) { try { this.permissions.setGroupFrom(collection.getPermissions()); } catch(final PermissionDeniedException pde) { throw new IllegalArgumentException(pde); //TODO improve } } } //TODO document really should not hold a reference to the brokerpool public BrokerPool getBrokerPool() { return pool; } /** * The method <code>getLocalPart</code> * * @return a <code>String</code> value */ @Override public String getLocalName() { return ""; } /** * The method <code>getNamespaceURI</code> * * @return a <code>String</code> value */ @Override public String getNamespaceURI() { return XMLConstants.NULL_NS_URI; } /************************************************ * * Document metadata * ************************************************/ /** * The method <code>getCollection</code> * * @return a <code>Collection</code> value */ public Collection getCollection() { return collection; } /** * The method <code>setCollection</code> * * @param parent a <code>Collection</code> value */ public void setCollection(final Collection parent) { this.collection = parent; } /** * The method <code>getDocId</code> * * @return an <code>int</code> value */ public int getDocId() { return docId; } /** * The method <code>setDocId</code> * * @param docId an <code>int</code> value */ public void setDocId(final int docId) { this.docId = docId; } /** * Returns the type of this resource, either {@link #XML_FILE} or * {@link #BINARY_FILE}. */ public byte getResourceType() { return XML_FILE; } /** * The method <code>getFileURI</code> * * @return a <code>XmldbURI</code> value */ public XmldbURI getFileURI() { //checkAvail(); return fileURI; } /** * The method <code>setFileURI</code> * * @param fileURI a <code>XmldbURI</code> value */ public void setFileURI(final XmldbURI fileURI) { this.fileURI = fileURI; } @Override public XmldbURI getURI() { if(collection == null) { return fileURI; } else { return collection.getURI().append(fileURI); } } public boolean isCollectionConfig() { return fileURI.endsWith(CollectionConfiguration.COLLECTION_CONFIG_SUFFIX_URI); } @Override public Permission getPermissions() { return permissions; } /** * The method <code>setMode</code> * * @param perm a <code>Permission</code> value * @deprecated This function is considered a security problem * and should be removed, move code to copyOf or Constructor */ @Deprecated public void setPermissions(final Permission perm) { permissions = perm; } /** * The method <code>setMetadata</code> * * @param meta a <code>DocumentMetadata</code> value * @deprecated This function is considered a security problem * and should be removed, move code to copyOf or Constructor */ @Deprecated public void setMetadata(final DocumentMetadata meta) { this.metadata = meta; } @Override public DocumentMetadata getMetadata() { if(metadata == null) { try(final DBBroker broker = pool.getBroker()) { broker.getResourceMetadata(this); } catch(final EXistException e) { LOG.warn("Error while loading document metadata: " + e.getMessage(), e); } } return metadata; } /************************************************ * * Persistent node methods * ************************************************/ /** * Copy the relevant internal fields from the specified document object. * This is called by {@link Collection} when replacing a document. * * @param other a <code>DocumentImpl</code> value * @param preserve Cause copyOf to preserve the following attributes of * each source file in the copy: modification time, * access time, file mode, user ID, and group ID, * as allowed by permissions and Access Control * Lists (ACLs) */ public void copyOf(final DocumentImpl other, final boolean preserve) { childAddress = null; children = 0; //XXX: why reusing? better to create new instance? -shabanovd metadata = getMetadata(); if(metadata == null) { metadata = new DocumentMetadata(); } //copy metadata metadata.copyOf(other.getMetadata()); if(preserve) { //copy permission permissions = ((UnixStylePermission) other.permissions).copy(); //created and last modified are done by metadata.copyOf //metadata.setCreated(other.getMetadata().getCreated()); //metadata.setLastModified(other.getMetadata().getLastModified()); } else { //update timestamp final long timestamp = System.currentTimeMillis(); metadata.setCreated(timestamp); metadata.setLastModified(timestamp); } // reset pageCount: will be updated during storage metadata.setPageCount(0); } /** * The method <code>copyChildren</code> * * @param other a <code>DocumentImpl</code> value */ public void copyChildren(final DocumentImpl other) { childAddress = other.childAddress; children = other.children; } /** * Returns true if the document is currently locked for * write. */ public synchronized boolean isLockedForWrite() { return getUpdateLock().isLockedForWrite(); } /** * Returns the update lock associated with this * resource. */ public final synchronized Lock getUpdateLock() { if(updateLock == null) { updateLock = new MultiReadReentrantLock(fileURI); } return updateLock; } /** * The method <code>setUserLock</code> * * @param user an <code>User</code> value */ public void setUserLock(final Account user) { getMetadata().setUserLock(user == null ? 0 : user.getId()); } /** * The method <code>getUserLock</code> * * @return an <code>User</code> value */ public Account getUserLock() { final int lockOwnerId = getMetadata().getUserLock(); if(lockOwnerId == 0) { return null; } final SecurityManager secman = pool.getSecurityManager(); return secman.getAccount(lockOwnerId); } /** * Returns the estimated size of the data in this document. * <p/> * As an estimation, the number of pages occupied by the document * is multiplied with the current page size. */ public long getContentLength() { final long length = getMetadata().getPageCount() * pool.getPageSize(); return (length < 0) ? 0 : length; } /** * The method <code>triggerDefrag</code> */ public void triggerDefrag() { int fragmentationLimit = -1; final Object property = pool.getConfiguration().getProperty(DBBroker.PROPERTY_XUPDATE_FRAGMENTATION_FACTOR); if(property != null) { fragmentationLimit = ((Integer) property).intValue(); } if(fragmentationLimit != -1) { getMetadata().setSplitCount(fragmentationLimit); } } /** * The method <code>getNode</code> * * @param nodeId a <code>NodeId</code> value * @return a <code>Node</code> value */ public Node getNode(final NodeId nodeId) { if(nodeId.getTreeLevel() == 1) { return getDocumentElement(); } try(final DBBroker broker = pool.getBroker()) { return broker.objectWith(this, nodeId); } catch(final EXistException e) { LOG.warn("Error occurred while retrieving node: " + e.getMessage(), e); } return null; } /** * The method <code>getNode</code> * * @param p a <code>NodeProxy</code> value * @return a <code>Node</code> value */ public Node getNode(final NodeProxy p) { if(p.getNodeId().getTreeLevel() == 1) { return getDocumentElement(); } try(final DBBroker broker = pool.getBroker()) { return broker.objectWith(p); } catch(final Exception e) { LOG.warn("Error occurred while retrieving node: " + e.getMessage(), e); } return null; } /** * The method <code>resizeChildList</code> */ private void resizeChildList() { final long[] newChildList = new long[children]; if(childAddress != null) { System.arraycopy(childAddress, 0, newChildList, 0, childAddress.length); } childAddress = newChildList; } /** * The method <code>appendChild</code> * * @param child a <code>NodeHandle</code> value * @throws DOMException if an error occurs */ public void appendChild(final NodeHandle child) throws DOMException { ++children; resizeChildList(); childAddress[children - 1] = child.getInternalAddress(); } /** * The method <code>write</code> * * @param ostream a <code>VariableByteOutputStream</code> value * @throws IOException if an error occurs */ public void write(final VariableByteOutputStream ostream) throws IOException { try { if(!getCollection().isTempCollection() && !getUpdateLock().isLockedForWrite()) { LOG.warn("document not locked for write !"); } ostream.writeInt(docId); ostream.writeUTF(fileURI.toString()); getPermissions().write(ostream); ostream.writeInt(children); if(children > 0) { for(int i = 0; i < children; i++) { ostream.writeInt(StorageAddress.pageFromPointer(childAddress[i])); ostream.writeShort(StorageAddress.tidFromPointer(childAddress[i])); } } getMetadata().write(pool.getSymbols(), ostream); } catch(final IOException e) { LOG.warn("io error while writing document data", e); //TODO : raise exception ? } } /** * The method <code>read</code> * * @param istream a <code>VariableByteInput</code> value * @throws IOException if an error occurs * @throws EOFException if an error occurs */ public void read(final VariableByteInput istream) throws IOException, EOFException { try { docId = istream.readInt(); fileURI = XmldbURI.createInternal(istream.readUTF()); getPermissions().read(istream); //Should be > 0 ;-) children = istream.readInt(); childAddress = new long[children]; for(int i = 0; i < children; i++) { childAddress[i] = StorageAddress.createPointer(istream.readInt(), istream.readShort()); } } catch(final IOException e) { LOG.error("IO error while reading document data for document " + fileURI, e); //TODO : raise exception ? } } public void readWithMetadata(final VariableByteInput istream) throws IOException, EOFException { try { docId = istream.readInt(); fileURI = XmldbURI.createInternal(istream.readUTF()); getPermissions().read(istream); //Should be > 0 ;-) children = istream.readInt(); childAddress = new long[children]; for(int i = 0; i < children; i++) { childAddress[i] = StorageAddress.createPointer(istream.readInt(), istream.readShort()); } metadata = new DocumentMetadata(); metadata.read(pool.getSymbols(), istream); } catch(final IOException e) { LOG.error("IO error while reading document data for document " + fileURI, e); //TODO : raise exception ? } } /** * The method <code>readDocumentMeta</code> * * @param istream a <code>VariableByteInput</code> value */ public void readDocumentMeta(final VariableByteInput istream) { // skip over already known document data try { istream.skip(1); //docId istream.readUTF(); //fileURI.toString() //istream.skip(2 + 2); //uid, gid, mode, children count istream.skip(1); //unix style permission uses a single long if(permissions instanceof ACLPermission) { final int aceCount = istream.read(); istream.skip(aceCount); } istream.skip(1); //children size istream.skip(children * 2); //actual children metadata = new DocumentMetadata(); metadata.read(pool.getSymbols(), istream); } catch(final IOException e) { LOG.error("IO error while reading document metadata for " + fileURI, e); //TODO : raise exception ? } } /** * The method <code>compareTo</code> * * @param other an <code>DocumentImpl</code> value * @return an <code>int</code> value */ @Override public final int compareTo(final DocumentImpl other) { final long otherId = other.docId; if(otherId == docId) { return Constants.EQUAL; } else if(docId < otherId) { return Constants.INFERIOR; } else { return Constants.SUPERIOR; } } /* (non-Javadoc) * @see org.exist.dom.persistent.NodeImpl#updateChild(org.w3c.dom.Node, org.w3c.dom.Node) */ @Override public IStoredNode updateChild(final Txn transaction, final Node oldChild, final Node newChild) throws DOMException { if(!(oldChild instanceof StoredNode)) { throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Node does not belong to this document"); } final IStoredNode<?> oldNode = (IStoredNode<?>) oldChild; final IStoredNode<?> newNode = (IStoredNode<?>) newChild; final IStoredNode<?> previousNode = (IStoredNode<?>) oldNode.getPreviousSibling(); if(previousNode == null) { throw new DOMException(DOMException.NOT_FOUND_ERR, "No previous sibling for the old child"); } try(final DBBroker broker = pool.getBroker()) { if(oldChild.getNodeType() == Node.ELEMENT_NODE) { // replace the document-element //TODO : be more precise in the type test -pb if(newChild.getNodeType() != Node.ELEMENT_NODE) { throw new DOMException( DOMException.INVALID_MODIFICATION_ERR, "A node replacing the document root needs to be an element"); } broker.removeNode(transaction, oldNode, oldNode.getPath(), null); broker.endRemove(transaction); newNode.setNodeId(oldNode.getNodeId()); broker.insertNodeAfter(null, previousNode, newNode); final NodePath path = newNode.getPath(); broker.indexNode(transaction, newNode, path); broker.endElement(newNode, path, null); broker.flush(); } else { broker.removeNode(transaction, oldNode, oldNode.getPath(), null); broker.endRemove(transaction); newNode.setNodeId(oldNode.getNodeId()); broker.insertNodeAfter(transaction, previousNode, newNode); } } catch(final EXistException e) { LOG.warn("Exception while updating child node: " + e.getMessage(), e); //TODO : thow exception ? } return newNode; } @Override public Node getFirstChild() { if(children == 0) { return null; } try(final DBBroker broker = pool.getBroker()) { return broker.objectWith(new NodeProxy(this, NodeId.DOCUMENT_NODE, childAddress[0])); } catch(final EXistException e) { LOG.warn("Exception while inserting node: " + e.getMessage(), e); //TODO : throw exception ? } return null; } protected NodeProxy getFirstChildProxy() { return new NodeProxy(this, NodeId.ROOT_NODE, Node.ELEMENT_NODE, childAddress[0]); } /** * The method <code>getFirstChildAddress</code> * * @return a <code>long</code> value */ public long getFirstChildAddress() { if(children == 0) { return StoredNode.UNKNOWN_NODE_IMPL_ADDRESS; } return childAddress[0]; } /** * The method <code>getChildNodes</code> * * @return a <code>NodeList</code> value */ @Override public NodeList getChildNodes() { final org.exist.dom.NodeListImpl list = new org.exist.dom.NodeListImpl(); try(final DBBroker broker = pool.getBroker()) { for(int i = 0; i < children; i++) { final Node child = broker.objectWith(new NodeProxy(this, NodeId.DOCUMENT_NODE, childAddress[i])); list.add(child); } } catch(final EXistException e) { LOG.warn("Exception while retrieving child nodes: " + e.getMessage(), e); } return list; } /** * The method <code>getPreviousSibling</code> * * @param node a <code>NodeHanle</code> value * @return a <code>Node</code> value */ protected Node getPreviousSibling(final NodeHandle node) { final NodeList cl = getChildNodes(); for(int i = 0; i < cl.getLength(); i++) { final NodeHandle next = (NodeHandle) cl.item(i); if(StorageAddress.equals(node.getInternalAddress(), next.getInternalAddress())) { return i == 0 ? null : cl.item(i - 1); } } return null; } /** * The method <code>getFollowingSibling</code> * * @param node a <code>NodeHandle</code> value * @return a <code>Node</code> value */ protected Node getFollowingSibling(final NodeHandle node) { final NodeList cl = getChildNodes(); for(int i = 0; i < cl.getLength(); i++) { final NodeHandle next = (NodeHandle) cl.item(i); if(StorageAddress.equals(node, next)) { return i == children - 1 ? null : cl.item(i + 1); } } return null; } /** * The method <code>findElementsByTagName</code> * * @param root a <code>NodeHandle</code> value * @param qname a <code>QName</code> value * @return a <code>NodeList</code> value */ protected NodeList findElementsByTagName(final NodeHandle root, final QName qname) { try(final DBBroker broker = pool.getBroker()) { final MutableDocumentSet docs = new DefaultDocumentSet(); docs.add(this); final NodeProxy p = new NodeProxy(this, root.getNodeId(), root.getInternalAddress()); final NodeSelector selector = new DescendantSelector(p, Expression.NO_CONTEXT_ID); return broker.getStructuralIndex().findElementsByTagName(ElementValue.ELEMENT, docs, qname, selector, null); } catch(final Exception e) { LOG.warn("Exception while finding elements: " + e.getMessage(), e); } return NodeSet.EMPTY_SET; } /************************************************ * * NodeImpl methods * ************************************************/ /** * The method <code>getDoctype</code> * * @return a <code>DocumentType</code> value */ @Override public DocumentType getDoctype() { return getMetadata().getDocType(); } /** * The method <code>setDocumentType</code> * * @param docType a <code>DocumentType</code> value */ public void setDocumentType(final DocumentType docType) { getMetadata().setDocType(docType); } /** * The method <code>getOwnerDocument</code> * * @return a <code>Document</code> value */ @Override public DocumentImpl getOwnerDocument() { return this; } /** * The method <code>setOwnerDocument</code> * * @param doc a <code>Document</code> value */ public void setOwnerDocument(final Document doc) { if(doc != this) { throw new IllegalArgumentException("Can't set owner document"); } } /** * The method <code>getQName</code> * * @return a <code>QName</code> value */ @Override public QName getQName() { return QName.DOCUMENT_QNAME; } @Override public void setQName(final QName qname) { //do nothing } /** * The method <code>getNodeType</code> * * @return a <code>short</code> value */ @Override public short getNodeType() { return Node.DOCUMENT_NODE; } /** * The method <code>getPreviousSibling</code> * * @return a <code>Node</code> value */ @Override public Node getPreviousSibling() { //Documents don't have siblings return null; } /** * The method <code>getNextSibling</code> * * @return a <code>Node</code> value */ @Override public Node getNextSibling() { //Documents don't have siblings return null; } /** * The method <code>createAttribute</code> * * @param name a <code>String</code> value * @return an <code>Attr</code> value * @throws DOMException if an error occurs */ @Override public Attr createAttribute(final String name) throws DOMException { final AttrImpl attr = new AttrImpl(new QName(name), getBrokerPool().getSymbols()); attr.setOwnerDocument(this); return attr; } /** * The method <code>createAttributeNS</code> * * @param namespaceURI a <code>String</code> value * @param qualifiedName a <code>String</code> value * @return an <code>Attr</code> value * @throws DOMException if an error occurs */ @Override public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) throws DOMException { final AttrImpl attr = new AttrImpl(QName.parse(namespaceURI, qualifiedName), getBrokerPool().getSymbols()); attr.setOwnerDocument(this); return attr; } /** * The method <code>createElement</code> * * @param tagName a <code>String</code> value * @return an <code>Element</code> value * @throws DOMException if an error occurs */ @Override public Element createElement(final String tagName) throws DOMException { final ElementImpl element = new ElementImpl(new QName(tagName), getBrokerPool().getSymbols()); element.setOwnerDocument(this); return element; } /** * The method <code>createElementNS</code> * * @param namespaceURI a <code>String</code> value * @param qualifiedName a <code>String</code> value * @return an <code>Element</code> value * @throws DOMException if an error occurs */ @Override public Element createElementNS(final String namespaceURI, final String qualifiedName) throws DOMException { final ElementImpl element = new ElementImpl(QName.parse(namespaceURI, qualifiedName), getBrokerPool().getSymbols()); element.setOwnerDocument(this); return element; } /** * The method <code>createTextNode</code> * * @param data a <code>String</code> value * @return a <code>Text</code> value */ @Override public Text createTextNode(final String data) { final TextImpl text = new TextImpl(data); text.setOwnerDocument(this); return text; } /* * W3C Document-Methods */ /** * The method <code>getDocumentElement</code> * * @return an <code>Element</code> value */ @Override public Element getDocumentElement() { final NodeList cl = getChildNodes(); for(int i = 0; i < cl.getLength(); i++) { if(cl.item(i).getNodeType() == Node.ELEMENT_NODE) { return (Element) cl.item(i); } } return null; } /** * The method <code>getElementsByTagName</code> * * @param tagname a <code>String</code> value * @return a <code>NodeList</code> value */ @Override public NodeList getElementsByTagName(final String tagname) { return getElementsByTagNameNS(XMLConstants.NULL_NS_URI, tagname); } /** * The method <code>getElementsByTagNameNS</code> * * @param namespaceURI a <code>String</code> value * @param localName a <code>String</code> value * @return a <code>NodeList</code> value */ @Override public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) { try(final DBBroker broker = pool.getBroker()) { final MutableDocumentSet docs = new DefaultDocumentSet(); docs.add(this); final QName qname = new QName(localName, namespaceURI); return broker.getStructuralIndex().findElementsByTagName(ElementValue.ELEMENT, docs, qname, null, null); } catch(final Exception e) { LOG.warn("Exception while finding elements: " + e.getMessage(), e); //TODO : throw exception ? } return NodeSet.EMPTY_SET; } @Override public Node getParentNode() { //Documents don't have parents return null; } /** * The method <code>getChildCount</code> * * @return an <code>int</code> value */ @Override public int getChildCount() { return children; } public void setChildCount(final int count) { this.children = count; if(children == 0) { this.childAddress = null; } } @Override public boolean isSameNode(final Node other) { // This function is used by Saxon in some circumstances, and this partial implementation is required for proper Saxon operation. if(other instanceof DocumentImpl) { return this.docId == ((DocumentImpl) other).getDocId(); } else { return false; } } @Override public CDATASection createCDATASection(final String data) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "createCDATASection not implemented on class " + getClass().getName()); } @Override public Comment createComment(final String data) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "createComment not implemented on class " + getClass().getName()); } @Override public DocumentFragment createDocumentFragment() throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "createDocumentFragment not implemented on class " + getClass().getName()); } @Override public EntityReference createEntityReference(final String name) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "createEntityReference not implemented on class " + getClass().getName()); } @Override public ProcessingInstruction createProcessingInstruction(final String target, final String data) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "createProcessingInstruction not implemented on class " + getClass().getName()); } @Override public Element getElementById(final String elementId) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getElementById not implemented on class " + getClass().getName()); } @Override public org.w3c.dom.DOMImplementation getImplementation() { return new StoredDOMImplementation(); } @Override public boolean getStrictErrorChecking() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getStrictErrorChecking not implemented on class " + getClass().getName()); } @Override public Node adoptNode(final Node node) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "adoptNode not implemented on class " + getClass().getName()); } @Override public Node importNode(final Node importedNode, final boolean deep) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "importNode not implemented on class " + getClass().getName()); } @Override public void setStrictErrorChecking(final boolean strict) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setStrictErrorChecking not implemented on class " + getClass().getName()); } @Override public String getInputEncoding() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "String getInputEncoding not implemented on class " + getClass().getName()); } @Override public String getXmlEncoding() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getXmlEncoding not implemented on class " + getClass().getName()); } @Override public boolean getXmlStandalone() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getXmlStandalone not implemented on class " + getClass().getName()); } @Override public void setXmlStandalone(final boolean xmlStandalone) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setXmlStandalone not implemented on class " + getClass().getName()); } @Override public String getXmlVersion() { return "1.0"; } @Override public void setXmlVersion(final String xmlVersion) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setXmlVersion not implemented on class " + getClass().getName()); } @Override public String getDocumentURI() { return getBaseURI(); } @Override public void setDocumentURI(final String documentURI) { //TODO : non-writable -pb throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "setDocumentURI not implemented on class " + getClass().getName()); } @Override public DOMConfiguration getDomConfig() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "getDomConfig not implemented on class " + getClass().getName()); } @Override public void normalizeDocument() { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "normalizeDocument not implemented on class " + getClass().getName()); } @Override public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "renameNode not implemented on class " + getClass().getName()); } @Override public String getBaseURI() { return getURI().toString(); } @Override public String toString() { return getURI() + " - <" + (getDocumentElement() != null ? getDocumentElement().getNodeName() : null) + ">"; } @Override public NodeId getNodeId() { return null; } }