diff --git a/exist-core/pom.xml b/exist-core/pom.xml
index 26c70b80f7b..286fb346b26 100644
--- a/exist-core/pom.xml
+++ b/exist-core/pom.xml
@@ -212,8 +212,8 @@
- cglib
- cglib
+ net.bytebuddy
+ byte-buddy
diff --git a/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java b/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java
index 347c219af7b..008e9beab5e 100644
--- a/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java
+++ b/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java
@@ -681,6 +681,8 @@ public static int nodeType2XQuery(final short nodeType) {
return Type.ATTRIBUTE;
case Node.TEXT_NODE:
return Type.TEXT;
+ case Node.CDATA_SECTION_NODE:
+ return Type.CDATA_SECTION;
case Node.PROCESSING_INSTRUCTION_NODE:
return Type.PROCESSING_INSTRUCTION;
case Node.COMMENT_NODE:
diff --git a/exist-core/src/main/java/org/exist/xmldb/LocalXMLResource.java b/exist-core/src/main/java/org/exist/xmldb/LocalXMLResource.java
index 17af5fbc530..5e473fae9c3 100644
--- a/exist-core/src/main/java/org/exist/xmldb/LocalXMLResource.java
+++ b/exist-core/src/main/java/org/exist/xmldb/LocalXMLResource.java
@@ -28,6 +28,8 @@
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -38,6 +40,10 @@
import javax.annotation.Nullable;
import javax.xml.transform.TransformerException;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.InvocationHandlerAdapter;
+import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.io.IOUtils;
import org.exist.dom.memtree.DocumentImpl;
import org.exist.dom.memtree.NodeImpl;
@@ -72,10 +78,6 @@
import com.evolvedbinary.j8fu.function.ConsumerE;
import com.evolvedbinary.j8fu.tuple.Tuple3;
-import net.sf.cglib.proxy.Enhancer;
-import net.sf.cglib.proxy.MethodInterceptor;
-import net.sf.cglib.proxy.MethodProxy;
-
/**
* Local implementation of XMLResource.
*/
@@ -329,20 +331,32 @@ private Node exportInternalNode(final Node node) {
throw new IllegalArgumentException("Provided node does not implement org.w3c.dom");
}
- final Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(domClazz.get());
- final Class[] interfaceClasses;
+ DynamicType.Builder extends Node> byteBuddyBuilder = new ByteBuddy()
+ .subclass(domClazz.get());
+
+ // these interfaces are just used to flag the node type (persistent or memtree) to make
+ // the implementation of {@link DOMMethodInterceptor} simpler.
if (node instanceof StoredNode) {
- interfaceClasses = new Class[]{domClazz.get(), StoredNodeIdentity.class};
+ byteBuddyBuilder = byteBuddyBuilder.implement(StoredNodeIdentity.class);
} else if (node instanceof org.exist.dom.memtree.NodeImpl) {
- interfaceClasses = new Class[]{domClazz.get(), MemtreeNodeIdentity.class};
- } else {
- interfaceClasses = new Class[] { domClazz.get() };
+ byteBuddyBuilder = byteBuddyBuilder.implement(MemtreeNodeIdentity.class);
}
- enhancer.setInterfaces(interfaceClasses);
- enhancer.setCallback(new DOMMethodInterceptor(node));
- return (Node)enhancer.create();
+ byteBuddyBuilder = byteBuddyBuilder
+ .method(ElementMatchers.any())
+ .intercept(InvocationHandlerAdapter.of(new DOMMethodInterceptor(node)));
+
+ try {
+ final Node nodeProxy = byteBuddyBuilder
+ .make()
+ .load(getClass().getClassLoader())
+ .getLoaded()
+ .getDeclaredConstructor().newInstance();
+
+ return nodeProxy;
+ } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
}
private Optional> getW3cNodeInterface(final Class extends Node> nodeClazz) {
@@ -352,7 +366,7 @@ private Optional> getW3cNodeInterface(final Class extend
.map(c -> (Class extends Node>)c);
}
- private class DOMMethodInterceptor implements MethodInterceptor {
+ public class DOMMethodInterceptor implements InvocationHandler {
private final Node node;
public DOMMethodInterceptor(final Node node) {
@@ -360,7 +374,9 @@ public DOMMethodInterceptor(final Node node) {
}
@Override
- public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable {
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+
+
/*
NOTE(AR): we have to take special care of eXist-db's
persistent and memtree DOM's node equality.
@@ -381,9 +397,9 @@ public Object intercept(final Object obj, final Method method, final Object[] ar
*/
Object domResult = null;
if(method.getName().equals("equals")
- && obj instanceof StoredNodeIdentity
+ && proxy instanceof StoredNodeIdentity
&& args.length == 1 && args[0] instanceof StoredNodeIdentity) {
- final StoredNodeIdentity ni1 = ((StoredNodeIdentity) obj);
+ final StoredNodeIdentity ni1 = ((StoredNodeIdentity) proxy);
final StoredNodeIdentity ni2 = ((StoredNodeIdentity) args[0]);
final Optional niEquals = ni1.getNodeId().flatMap(n1id -> ni2.getNodeId().map(n1id::equals));
@@ -391,9 +407,9 @@ public Object intercept(final Object obj, final Method method, final Object[] ar
domResult = niEquals.get();
}
} else if(method.getName().equals("equals")
- && obj instanceof MemtreeNodeIdentity
+ && proxy instanceof MemtreeNodeIdentity
&& args.length == 1 && args[0] instanceof MemtreeNodeIdentity) {
- final MemtreeNodeIdentity ni1 = ((MemtreeNodeIdentity) obj);
+ final MemtreeNodeIdentity ni1 = ((MemtreeNodeIdentity) proxy);
final MemtreeNodeIdentity ni2 = ((MemtreeNodeIdentity) args[0]);
final Optional niEquals = ni1.getNodeId().flatMap(n1id -> ni2.getNodeId().map(n2id -> n1id._1 == n2id._1 && n1id._2 == n2id._2 && n1id._3 == n2id._3));
@@ -401,12 +417,12 @@ public Object intercept(final Object obj, final Method method, final Object[] ar
domResult = niEquals.get();
}
} else if(method.getName().equals("getNodeId")) {
- if (obj instanceof StoredNodeIdentity
- && args.length == 0
+ if (proxy instanceof StoredNodeIdentity
+ && (args == null || args.length == 0)
&& node instanceof StoredNode) {
domResult = Optional.of(((StoredNode) node).getNodeId());
- } else if (obj instanceof MemtreeNodeIdentity
- && args.length == 0
+ } else if (proxy instanceof MemtreeNodeIdentity
+ && (args == null || args.length == 0)
&& node instanceof org.exist.dom.memtree.NodeImpl) {
final org.exist.dom.memtree.NodeImpl memtreeNode = (org.exist.dom.memtree.NodeImpl) node;
domResult = Optional.of(Tuple(memtreeNode.getOwnerDocument(), memtreeNode.getNodeNumber(), memtreeNode.getNodeType()));
@@ -447,7 +463,7 @@ public int getLength() {
* Used by {@link DOMMethodInterceptor} to
* help with equality of persistent DOM nodes.
*/
- private interface StoredNodeIdentity {
+ public interface StoredNodeIdentity {
Optional getNodeId();
}
@@ -455,7 +471,7 @@ private interface StoredNodeIdentity {
* Used by {@link DOMMethodInterceptor} to
* help with equality of memtree DOM nodes.
*/
- private interface MemtreeNodeIdentity {
+ public interface MemtreeNodeIdentity {
Optional> getNodeId();
}
diff --git a/exist-parent/pom.xml b/exist-parent/pom.xml
index 63b77c4ff30..ec55e09682f 100644
--- a/exist-parent/pom.xml
+++ b/exist-parent/pom.xml
@@ -439,9 +439,9 @@
- cglib
- cglib
- 3.3.0
+ net.bytebuddy
+ byte-buddy
+ 1.14.4
@@ -755,7 +755,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.8
+ 0.8.9
jacocoArgLine
diff --git a/extensions/exquery/restxq/pom.xml b/extensions/exquery/restxq/pom.xml
index 78f0db66c72..e1ab8b81232 100644
--- a/extensions/exquery/restxq/pom.xml
+++ b/extensions/exquery/restxq/pom.xml
@@ -71,8 +71,8 @@
- cglib
- cglib
+ net.bytebuddy
+ byte-buddy
diff --git a/extensions/exquery/restxq/src/main/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapter.java b/extensions/exquery/restxq/src/main/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapter.java
index 4e5a6473a3f..0beda2ecf8f 100644
--- a/extensions/exquery/restxq/src/main/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapter.java
+++ b/extensions/exquery/restxq/src/main/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapter.java
@@ -26,20 +26,26 @@
*/
package org.exist.extensions.exquery.restxq.impl.adapters;
-import net.sf.cglib.proxy.Callback;
-import net.sf.cglib.proxy.CallbackFilter;
-import net.sf.cglib.proxy.Dispatcher;
-import net.sf.cglib.proxy.Enhancer;
-import net.sf.cglib.proxy.NoOp;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.matcher.ElementMatchers;
import org.exist.dom.persistent.NodeHandle;
import org.exist.dom.persistent.NodeProxy;
+import org.exist.xquery.Expression;
import org.exist.xquery.value.Type;
import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
+import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
+import java.lang.reflect.InvocationTargetException;
+
+
/**
* A NodeProxy Proxy which enhances NodeProxy
* with a W3C DOM implementation by proxying to
@@ -49,103 +55,49 @@
* @author Adam Retter
*/
class DomEnhancingNodeProxyAdapter {
-
+
public static NodeProxy create(final NodeProxy nodeProxy) {
-
- final Class extends Node> clazzes[] = getNodeClasses(nodeProxy);
-
- // NoOp Callback is for NodeProxy calls
- // NodeDispatched Callback is for the underlying Node calls
- final Callback[] callbacks = {
- NoOp.INSTANCE,
- new NodeDispatcher(nodeProxy)
- };
-
- final CallbackFilter callbackFilter = method -> {
- final Class declaringClass = method.getDeclaringClass();
+ final Class extends Node> domClazz = getNodeClass(nodeProxy);
- //look for nodes
- boolean isMethodOnNode = false;
- if(declaringClass.equals(Node.class)) {
- isMethodOnNode = true;
- } else {
- //search parent interfaces
- for(final Class iface : declaringClass.getInterfaces()) {
- if(iface.equals(Node.class)) {
- isMethodOnNode = true;
- break;
- }
- }
- }
+ final DynamicType.Builder extends NodeProxy> byteBuddyBuilder = new ByteBuddy()
+ .subclass(NodeProxy.class)
+ .implement(domClazz)
- if(isMethodOnNode) {
- return 1; //The NodeDispatcher
- } else {
- return 0; //The NoOp pass through
- }
- };
-
- final Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(NodeProxy.class);
- enhancer.setInterfaces(clazzes);
- enhancer.setCallbackFilter(callbackFilter);
- enhancer.setCallbacks(callbacks);
-
- return (NodeProxy)enhancer.create(
- new Class[] {
- NodeHandle.class
- },
- new Object[] {
- nodeProxy
- });
- }
-
- private static Class extends Node>[] getNodeClasses(final NodeProxy nodeProxy) {
- switch(nodeProxy.getType()) {
-
- case Type.DOCUMENT:
- return new Class[] {
- Document.class,
- Node.class
- };
-
- case Type.ELEMENT:
- return new Class[] {
- Element.class,
- Node.class
- };
+ .method(ElementMatchers.isDeclaredBy(NodeProxy.class))
+ .intercept(MethodCall.invokeSelf().on(nodeProxy).withAllArguments())
- case Type.ATTRIBUTE:
- return new Class[] {
- Attr.class,
- Node.class
- };
-
- case Type.TEXT:
- return new Class[] {
- Text.class,
- Node.class
- };
+ .method(ElementMatchers.isDeclaredBy(domClazz).or(ElementMatchers.isDeclaredBy(Node.class)))
+ .intercept(MethodCall.invokeSelf().on(nodeProxy.getNode()).withAllArguments())
- default:
- return new Class[] {
- Node.class
- };
+ .method(ElementMatchers.isHashCode())
+ .intercept(MethodCall.invokeSelf().on(nodeProxy));
+
+ try {
+ final NodeProxy nodeProxyProxy = byteBuddyBuilder
+ .make()
+ .load(nodeProxy.getClass().getClassLoader())
+ .getLoaded()
+ .getDeclaredConstructor(Expression.class, NodeHandle.class)
+ .newInstance(nodeProxy.getExpression(), nodeProxy);
+
+ return nodeProxyProxy;
+ } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException |
+ InvocationTargetException e) {
+ throw new IllegalStateException(e.getMessage(), e);
}
}
-
- public static class NodeDispatcher implements Dispatcher {
-
- private final NodeProxy nodeProxy;
-
- public NodeDispatcher(final NodeProxy nodeProxy) {
- this.nodeProxy = nodeProxy;
- }
-
- @Override
- public Object loadObject() throws Exception {
- return nodeProxy.getNode();
- }
+
+ private static Class extends Node> getNodeClass(final NodeProxy nodeProxy) {
+ return switch (nodeProxy.getType()) {
+ case Type.ELEMENT -> Element.class;
+ case Type.ATTRIBUTE -> Attr.class;
+ case Type.TEXT -> Text.class;
+ case Type.PROCESSING_INSTRUCTION -> ProcessingInstruction.class;
+ case Type.COMMENT -> Comment.class;
+ case Type.DOCUMENT -> Document.class;
+ case Type.CDATA_SECTION -> CDATASection.class;
+ default -> Node.class;
+ };
}
-}
\ No newline at end of file
+}
diff --git a/extensions/exquery/restxq/src/test/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapterTest.java b/extensions/exquery/restxq/src/test/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapterTest.java
new file mode 100644
index 00000000000..c27ace73466
--- /dev/null
+++ b/extensions/exquery/restxq/src/test/java/org/exist/extensions/exquery/restxq/impl/adapters/DomEnhancingNodeProxyAdapterTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright © 2001, Adam Retter
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.exist.extensions.exquery.restxq.impl.adapters;
+
+import com.evolvedbinary.j8fu.function.ConsumerE;
+import org.exist.EXistException;
+import org.exist.collections.Collection;
+import org.exist.dom.persistent.*;
+import org.exist.numbering.DLN;
+import org.exist.numbering.NodeId;
+import org.exist.security.PermissionDeniedException;
+import org.exist.storage.BrokerPool;
+import org.exist.storage.DBBroker;
+import org.exist.storage.lock.Lock;
+import org.exist.storage.txn.Txn;
+import org.exist.test.ExistEmbeddedServer;
+import org.exist.util.LockException;
+import org.exist.util.MimeType;
+import org.exist.util.StringInputSource;
+import org.exist.xmldb.XmldbURI;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.w3c.dom.*;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Adam Retter
+ */
+public class DomEnhancingNodeProxyAdapterTest {
+
+ @ClassRule
+ public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true);
+
+ private static final XmldbURI TEST_COLLECTION_URI = XmldbURI.DB.append("dom-enhancing-test");
+ private static final XmldbURI TEST_DOC_URI = XmldbURI.create("test-doc.xml");
+
+ private static final String TEST_DOC = """
+
+ text1
+
+
+
+ """;
+
+ @BeforeClass
+ public static void setup() throws EXistException, PermissionDeniedException, IOException, SAXException, LockException {
+ final BrokerPool brokerPool = existEmbeddedServer.getBrokerPool();
+ try (final Txn transaction = brokerPool.getTransactionManager().beginTransaction();
+ final DBBroker broker = brokerPool.get(Optional.of(brokerPool.getSecurityManager().getSystemSubject()));
+ final Collection collection = broker.getOrCreateCollection(transaction, TEST_COLLECTION_URI)) {
+
+ collection.storeDocument(transaction, broker, TEST_DOC_URI, new StringInputSource(TEST_DOC), MimeType.XML_TYPE);
+
+ // async release of collection lock
+ collection.close();
+
+ transaction.commit();
+ }
+ }
+
+
+ @Test
+ public void asElement() throws PermissionDeniedException, EXistException {
+ final NodeId elementId1 = new DLN("1");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, elementId1, Element.class, ElementImpl.class, elementProxy ->
+ assertEquals("test-doc", elementProxy.getNodeName())
+ )
+ );
+
+ final NodeId elementId2 = new DLN("1.2");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, elementId2, Element.class, ElementImpl.class, elementProxy ->
+ assertEquals("element1", elementProxy.getNodeName())
+ )
+ );
+ }
+
+ @Test
+ public void asAttr() throws PermissionDeniedException, EXistException {
+ final NodeId attrId = new DLN("1.2.1");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, attrId, Attr.class, AttrImpl.class, attrProxy ->
+ assertEquals("attr1", attrProxy.getNodeName())
+ )
+ );
+ }
+
+ @Test
+ public void asText() throws PermissionDeniedException, EXistException {
+ final NodeId textId = new DLN("1.2.2");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, textId, Text.class, TextImpl.class, textProxy ->
+ assertEquals("text1", textProxy.getTextContent())
+ )
+ );
+ }
+
+ @Test
+ public void asComment() throws PermissionDeniedException, EXistException {
+ final NodeId commentId = new DLN("1.4");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, commentId, Comment.class, CommentImpl.class, commentProxy ->
+ assertEquals("comment1", commentProxy.getTextContent())
+ )
+ );
+ }
+
+ @Test
+ public void asCdataSection() throws PermissionDeniedException, EXistException {
+ final DLN cdataSectionId = new DLN("1.6");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, cdataSectionId, CDATASection.class, CDATASectionImpl.class, cdataSectionProxy ->
+ assertEquals("cdata1", cdataSectionProxy.getTextContent())
+ )
+ );
+ }
+
+ @Test
+ public void asProcessingInstruction() throws PermissionDeniedException, EXistException {
+ final DLN processingInstructionId = new DLN("1.8");
+ withTestDocument(doc ->
+ assertProxiedCorrectly(doc, processingInstructionId, ProcessingInstruction.class, ProcessingInstructionImpl.class, processingInstructionProxy ->
+ assertEquals("pi1", processingInstructionProxy.getData())
+ )
+ );
+ }
+
+ private static void withTestDocument(final ConsumerE fnDocument) throws AssertionError, EXistException, PermissionDeniedException {
+ final BrokerPool brokerPool = existEmbeddedServer.getBrokerPool();
+ try (final Txn transaction = brokerPool.getTransactionManager().beginTransaction();
+ final DBBroker broker = brokerPool.get(Optional.of(brokerPool.getSecurityManager().getSystemSubject()));
+ final LockedDocument lockedDocument = broker.getXMLResource(TEST_COLLECTION_URI.append(TEST_DOC_URI), Lock.LockMode.READ_LOCK)) {
+
+ final DocumentImpl doc = lockedDocument.getDocument();
+
+ fnDocument.accept(doc);
+
+ transaction.commit();
+ }
+ }
+
+ private static void assertProxiedCorrectly(final DocumentImpl doc, final NodeId nodeId, final Class domInterfaceType, final Class extends NodeImpl> existDomImplementationType, final ConsumerE proxiedDomTypeAssertions) throws AssertionError {
+ final NodeProxy nodeProxy = new NodeProxy(doc, nodeId);
+ nodeProxy.getNode(); // NOTE(AR) causes type of the node proxy to be set
+
+ // check type of original
+ assertTrue("Expected instanceof NodeProxy", nodeProxy instanceof NodeProxy);
+ assertFalse("Expected not(instanceof " + domInterfaceType.getSimpleName() + ")", domInterfaceType.isInstance(nodeProxy));
+
+ // the function under test
+ final NodeProxy nodeProxyProxy = DomEnhancingNodeProxyAdapter.create(nodeProxy);
+
+ // check type of proxy
+ assertTrue("Expected instanceof NodeProxy", nodeProxyProxy instanceof NodeProxy);
+ assertTrue("Expected instanceof " + domInterfaceType.getSimpleName() + "; W3C Node type was: " + nodeProxyProxy.getNodeType(), domInterfaceType.isInstance(nodeProxyProxy));
+
+ // check W3C DOM Interface methods of proxy
+ proxiedDomTypeAssertions.accept((DT) nodeProxyProxy);
+
+ // check eXist-db NodeProxy methods of proxy
+ assertEquals(nodeId, nodeProxyProxy.getNodeId());
+ assertTrue("Expected instanceof " + existDomImplementationType.getSimpleName(), existDomImplementationType.isInstance(nodeProxyProxy.getNode()));
+ }
+}