Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions exist-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>

<dependency>
Expand Down
96 changes: 60 additions & 36 deletions exist-core/src/main/java/org/exist/xmldb/LocalXMLResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,27 @@
*/
package org.exist.xmldb;

import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Stream;

import javax.annotation.Nullable;
import javax.xml.transform.TransformerException;

import com.evolvedbinary.j8fu.function.ConsumerE;
import com.evolvedbinary.j8fu.tuple.Tuple3;
import net.sf.cglib.proxy.*;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.io.output.StringBuilderWriter;
import org.exist.dom.memtree.DocumentImpl;
import org.exist.dom.persistent.NodeProxy;
Expand All @@ -72,25 +90,17 @@
import org.w3c.dom.DocumentType;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.*;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.ext.LexicalHandler;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.XMLResource;
import xyz.elemental.mediatype.MediaType;

import javax.annotation.Nullable;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Stream;

import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
* Local implementation of XMLResource.
*/
Expand Down Expand Up @@ -281,20 +291,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<Class<? extends Node>> getW3cNodeInterface(final Class<? extends Node> nodeClazz) {
Expand All @@ -304,15 +326,17 @@ private Optional<Class<? extends Node>> 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) {
this.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.
Expand All @@ -333,32 +357,32 @@ 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<Boolean> niEquals = ni1.getNodeId().flatMap(n1id -> ni2.getNodeId().map(n1id::equals));
if (niEquals.isPresent()) {
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<Boolean> niEquals = ni1.getNodeId().flatMap(n1id -> ni2.getNodeId().map(n2id -> n1id._1 == n2id._1 && n1id._2 == n2id._2 && n1id._3 == n2id._3));
if (niEquals.isPresent()) {
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()));
Expand Down Expand Up @@ -399,15 +423,15 @@ public int getLength() {
* Used by {@link DOMMethodInterceptor} to
* help with equality of persistent DOM nodes.
*/
private interface StoredNodeIdentity {
public interface StoredNodeIdentity {
Optional<NodeId> getNodeId();
}

/**
* Used by {@link DOMMethodInterceptor} to
* help with equality of memtree DOM nodes.
*/
private interface MemtreeNodeIdentity {
public interface MemtreeNodeIdentity {
Optional<Tuple3<DocumentImpl, Integer, Short>> getNodeId();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@
package org.exist.xquery.functions.fn;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import net.sf.saxon.Configuration;
import net.sf.saxon.om.Item;
import net.sf.saxon.regex.RegexIterator;
Expand All @@ -80,22 +82,18 @@
public class FunAnalyzeString extends BasicFunction {

/**
* Implements a cglib MethodInterceptor to implement the `characters`
* Implements a ByteBuddy Invocation Handler to implement the `characters`
* method of Saxon 9's net.sf.saxon.regex.RegexIterator$MatchHandler
* and Saxon 12's net.sf.saxon.regex.RegexMatchHandler
*/
private static final MethodInterceptor CHARACTERS_INTERCEPTOR = (final Object obj, final Method method, final Object[] args, final MethodProxy proxy) -> {
if ("characters".equals(method.getName())) {
final MemTreeBuilder builder = ((AbstractSaxonRegexMatchHandler) obj).builder;
builder.characters(args[0].toString());
return null;
}
return proxy.invokeSuper(obj, args);
private static final InvocationHandler CHARACTERS_HANDLER = (final Object proxy, final Method method, final Object[] args) -> {
final MemTreeBuilder builder = ((AbstractSaxonRegexMatchHandler) proxy).builder;
final Object s = args[0];
builder.characters(s.toString());
return null;
};

@SuppressWarnings("unchecked")
private static final Class<? extends AbstractSaxonRegexMatchHandler> SAXON_MATCH_HANDLER_CLASS = createSaxonMatchHandlerClass();
private static final Constructor<? extends AbstractSaxonRegexMatchHandler> SAXON_MATCH_HANDLER_CLASS_CONSTRUCTOR = getSaxonMatchHandlerClassConstructor(SAXON_MATCH_HANDLER_CLASS);
private static final Constructor<? extends AbstractSaxonRegexMatchHandler> SAXON_MATCH_HANDLER_CLASS_CONSTRUCTOR = getSaxonMatchHandlerClassConstructor(createSaxonMatchHandlerClass());
private static final Method SAXON_PROCESS_MATCHING_SUBSTRING_FN = getSaxonProcessMatchingSubstringFunction();

private final static QName fnAnalyzeString = new QName("analyze-string", FnModule.NAMESPACE_URI);
Expand Down Expand Up @@ -217,15 +215,10 @@ private void match(final MemTreeBuilder builder, final RegexIterator regexIterat
builder.startElement(QN_MATCH, null);

try {
Enhancer.registerCallbacks(SAXON_MATCH_HANDLER_CLASS, new Callback[]{ CHARACTERS_INTERCEPTOR });
try {
final AbstractSaxonRegexMatchHandler matchHandler = SAXON_MATCH_HANDLER_CLASS_CONSTRUCTOR.newInstance(builder);
SAXON_PROCESS_MATCHING_SUBSTRING_FN.invoke(regexIterator, matchHandler);
} finally {
Enhancer.registerCallbacks(SAXON_MATCH_HANDLER_CLASS, null);
}
final AbstractSaxonRegexMatchHandler matchHandler = SAXON_MATCH_HANDLER_CLASS_CONSTRUCTOR.newInstance(builder);
SAXON_PROCESS_MATCHING_SUBSTRING_FN.invoke(regexIterator, matchHandler);
} catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new net.sf.saxon.trans.XPathException("Unable to dynamically invoke net.sf.saxon.regex.RegexIterator#processMatchingSubstring: " + e.getMessage(), e);
throw new net.sf.saxon.trans.XPathException("Unable to dynamically invoke net.sf.saxon.regex.RegexIterator#processMatchingSubsOkay,tring: " + e.getMessage(), e);
}

builder.endElement();
Expand Down Expand Up @@ -287,13 +280,17 @@ private static Class<? extends AbstractSaxonRegexMatchHandler> createSaxon9Match
return createSaxonMatchHandlerClass(matchHandlerInterfaceClazz);
}

@SuppressWarnings("unchecked")
private static Class<? extends AbstractSaxonRegexMatchHandler> createSaxonMatchHandlerClass(final Class<?> saxonMatchHandlerInterface) {
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(AbstractSaxonRegexMatchHandler.class);
enhancer.setInterfaces(new Class[]{saxonMatchHandlerInterface});
enhancer.setCallbackType(MethodInterceptor.class);
return (Class<? extends AbstractSaxonRegexMatchHandler>) enhancer.createClass();
private static Class<? extends AbstractSaxonRegexMatchHandler> createSaxonMatchHandlerClass(final Class<?> saxonMatchHandlerInterface) throws IllegalStateException{
try (final DynamicType.Unloaded<AbstractSaxonRegexMatchHandler> unloadedAbstractSaxonRegexMatchHandler = new ByteBuddy().subclass(AbstractSaxonRegexMatchHandler.class)
.implement(saxonMatchHandlerInterface)
.method(ElementMatchers.named("characters"))
.intercept(InvocationHandlerAdapter.of(CHARACTERS_HANDLER))
.make()) {

return unloadedAbstractSaxonRegexMatchHandler
.load(AbstractSaxonRegexMatchHandler.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions exist-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@
<version>3.3.0</version>
</dependency>

<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.18.2</version>
</dependency>

<dependency>
<groupId>org.exquery</groupId>
<artifactId>exquery-common</artifactId>
Expand Down