diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java index 8ae6be6143..8bb0a9fcb9 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Transform.java @@ -53,6 +53,9 @@ import net.sf.saxon.s9api.*; import net.sf.saxon.serialize.SerializationProperties; import net.sf.saxon.trans.UncheckedXPathException; + +import java.io.IOException; +import java.io.StringWriter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.dom.QName; @@ -67,8 +70,10 @@ import org.w3c.dom.Node; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.xml.transform.ErrorListener; import javax.xml.transform.Source; +import javax.xml.transform.SourceLocator; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMSource; import java.net.URI; @@ -161,6 +166,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro } final Xslt30Transformer xslt30Transformer = xsltExecutable.load30(); + xslt30Transformer.setMessageListener(new XsltMessageListener(context.getBroker().getBrokerPool().getSaxonProcessor())); options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName()))); xslt30Transformer.setInitialTemplateParameters(options.templateParams, false); @@ -532,4 +538,53 @@ public PendingException(String message, Throwable cause) { super(message, cause); } } + + private static class XsltMessageListener implements MessageListener { + + private final Processor processor; + + public XsltMessageListener(final Processor processor) { + this.processor = processor; + } + + @Override + public void message(final XdmNode content, final boolean terminate, final SourceLocator locator) { + + try (final StringWriter writer = new StringWriter()) { + final Serializer serializer = processor.newSerializer(); + serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes"); + serializer.setOutputWriter(writer); + serializer.serializeNode(content); + + @Nullable final String source; + final int sourceLine; + final int sourceColumn; + if (locator != null) { + source = locator.getSystemId(); + sourceLine = locator.getLineNumber(); + sourceColumn = locator.getColumnNumber(); + } else { + source = null; + sourceLine = -1; + sourceColumn = -1; + } + + final StringBuilder tag = new StringBuilder(""); + + LOGGER.info("{}{}", tag, writer.toString()); + } catch (final SaxonApiException e) { + LOGGER.error("Unable to serialize xsl:message content", e); + } catch (final IOException e) { + LOGGER.error("Unable to close xsl:message writer", e); + } + } + } } diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java index 43b9a63732..9e8e4ea70d 100644 --- a/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java +++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/transform/FunTransformITTest.java @@ -21,6 +21,12 @@ package org.exist.xquery.functions.fn.transform; import com.evolvedbinary.j8fu.tuple.Tuple2; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.layout.PatternLayout; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.security.PermissionDeniedException; @@ -49,6 +55,8 @@ import javax.xml.transform.Source; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple; @@ -247,6 +255,45 @@ public void identityMixedMemoryAndPersistentDom() throws XPathException, Permiss expectQuery(IDENTITY_MIXED_XSLT_QUERY_5, expected); } + @Test + public void xslMessageIsLogged() throws EXistException, PermissionDeniedException, IOException, XPathException { + final CapturingAppender appender = new CapturingAppender(); + appender.start(); + + final Logger transformLogger = (Logger) LogManager.getLogger(Transform.class); + transformLogger.addAppender(appender); + + try { + final String query = + "fn:transform(map {\n" + + " \"stylesheet-text\": '\n" + + " \n" + + " Hello from XSLT\n" + + " \n" + + " ',\n" + + " \"source-node\": document { }\n" + + "})?output"; + + final BrokerPool pool = existEmbeddedServer.getBrokerPool(); + try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject())); + final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, new StringSource(query), false, null, null, null, null, null)) { + assertNotNull(queryResult.result); + } + + String logged = null; + for (final String message : appender.getMessages()) { + if (message.contains(" messages = new ArrayList<>(); + + CapturingAppender() { + super("capturing-appender", null, PatternLayout.createDefaultLayout(), false, Property.EMPTY_ARRAY); + } + + @Override + public void append(final LogEvent event) { + messages.add(event.getMessage().getFormattedMessage()); + } + + List getMessages() { + return messages; + } + } }