Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import net.sf.saxon.s9api.*;
import net.sf.saxon.serialize.SerializationProperties;
import net.sf.saxon.trans.UncheckedXPathException;
import java.io.StringWriter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.dom.QName;
Expand All @@ -67,8 +68,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;
Expand Down Expand Up @@ -161,6 +164,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
}

final Xslt30Transformer xslt30Transformer = xsltExecutable.load30();
xslt30Transformer.setMessageListener(new XsltMessageListener(context));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing in context just pass in the Saxon Processor, i.e. context.getBroker().getBrokerPool().getSaxonProcessor()


options.initialMode.ifPresent(qNameValue -> xslt30Transformer.setInitialMode(Convert.ToSaxon.of(qNameValue.getQName())));
xslt30Transformer.setInitialTemplateParameters(options.templateParams, false);
Expand Down Expand Up @@ -532,4 +536,54 @@ public PendingException(String message, Throwable cause) {
super(message, cause);
}
}

private static class XsltMessageListener implements MessageListener{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please always put a space character before the opening curly brace, e.g. MessageListener{ -> MessageListener {


private final XQueryContext context;

public XsltMessageListener(final XQueryContext context){
this.context = context;
}

public void message(XdmNode content, boolean terminate, SourceLocator locator){

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All variables and parameters should be marked final where possible please.


try{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of try here, because your StringWriter class implements AutoCloseable, please use a try-with-resources expression instead.

final StringWriter writer = new StringWriter();
final Serializer serializer = context.getBroker().getBrokerPool().getSaxonProcessor().newSerializer();
serializer.setOutputProperty(Serializer.Property.OMIT_XML_DECLARATION, "yes");
serializer.setOutputWriter(writer);
serializer.serializeNode(content);

@Nullable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reformat from:

@Nullable
final String source;

to:

@Nullable final String source;

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;
}

String tag = "<xsl:message terminate=\"" + terminate + "\"";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a StringBuilder here instead of String please

if (source != null) {
tag += " source=\"" + source + "\"";
}
if (sourceLine != -1) {
tag += " sourceLine=\"" + sourceLine + "\"";
tag += " sourceColumn=\"" + sourceColumn + "\"";
}
tag += ">";

LOGGER.info("{}{}</xsl:message>", tag, writer.toString());
} catch (final SaxonApiException e) {
LOGGER.error("Unable to serialize xsl:message content", e);
}


}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -49,7 +55,10 @@

import javax.xml.transform.Source;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove CopyOnWriteArrayList


import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
import static org.junit.Assert.*;
Expand Down Expand Up @@ -247,6 +256,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\": '<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" version=\"2.0\">\n" +
" <xsl:template match=\"/\">\n" +
" <xsl:message>Hello from XSLT</xsl:message>\n" +
" </xsl:template>\n" +
" </xsl:stylesheet>',\n" +
" \"source-node\": document { <in/> }\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("<xsl:message")) {
logged = message;
break;
}
}
assertNotNull("Expected an xsl:message log entry", logged);
assertTrue(logged.contains("Hello from XSLT"));
} finally {
transformLogger.removeAppender(appender);
}
}

private static void expectQuery(final String query, final Source expected) throws EXistException, XPathException, PermissionDeniedException, IOException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()));
Expand Down Expand Up @@ -298,4 +346,23 @@ private static void createCollection(final DBBroker broker, final Txn transactio
}
}
}


private static class CapturingAppender extends AbstractAppender {

private final List<String> 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<String> getMessages() {
return messages;
}
}
}
Loading