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
1 change: 1 addition & 0 deletions mycore-base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@
<dependency>
<groupId>net.sf.saxon</groupId>
<artifactId>Saxon-HE</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import net.sf.saxon.expr.instruct.TerminationException;

/**
* Transforms XML content using a static XSL stylesheet. The stylesheet is configured via
* <code>MCR.ContentTransformer.{ID}.Stylesheet</code>. You may choose your own instance of
Expand Down Expand Up @@ -273,7 +275,7 @@ public void transform(MCRContent source, OutputStream out, MCRParameterCollector
reader.parse(source.getInputSource());
} catch (TransformerConfigurationException | SAXException | IllegalArgumentException
| ParserConfigurationException e) {
throw new IOException(e);
throw new IOException(getErrorMessage(e), e);
} catch (RuntimeException e) {
if (el != null && e.getCause() == null && el.getExceptionThrown() != null) {
//typically if a RuntimeException has no cause,
Expand All @@ -286,6 +288,18 @@ public void transform(MCRContent source, OutputStream out, MCRParameterCollector
}
}

/**
* Returns the message for an exception wrapping the given exception. Saxon does not include the text of a
* terminating <code>xsl:message</code> in its exception message, so it is added here.
*/
private static String getErrorMessage(Exception e) {
if (e.getCause() instanceof TerminationException terminationException) {
return "Error while XSL transformation: " + MCRErrorListener.getMyMessageAndLocation(terminationException);
}
// same message as for new IOException(e) or new MCRException(e)
return e.toString();
}

protected MCRContent transform(MCRContent source, XMLReader reader, TransformerHandler transformerHandler,
MCRParameterCollector parameter)
throws IOException, SAXException, TransformerException, ParserConfigurationException {
Expand Down Expand Up @@ -332,11 +346,11 @@ protected Deque<TransformerHandler> getTransformHandlers(MCRParameterCollector p
throws TransformerConfigurationException, SAXException, ParserConfigurationException {
checkTemplateUptodate();
Deque<TransformerHandler> xslSteps = new ArrayDeque<>();
//every transformhandler shares the same ErrorListener instance
//every transformhandler gets its own ErrorListener instance
for (Templates template : templates) {
TransformerHandler handler = tFactory.newTransformerHandler(template);
parameterCollector.setParametersTo(handler.getTransformer());
handler.getTransformer().setErrorListener(new MCRErrorListener());
MCRErrorListener.setTo(handler.getTransformer());
if (!xslSteps.isEmpty()) {
Result result = new SAXResult(handler);
xslSteps.getLast().setResult(result);
Expand Down Expand Up @@ -436,7 +450,7 @@ public MCRContent getBaseContent() {
transformed.setMimeType(mimeType);
transformed.setEncoding(encoding);
} catch (IOException | SAXException e) {
throw new MCRException(e);
throw new MCRException(getErrorMessage(e), e);
} catch (RuntimeException e) {
MCRErrorListener el = (MCRErrorListener) transformerHandler.getTransformer().getErrorListener();
if (el != null && e.getCause() == null && el.getExceptionThrown() != null) {
Expand Down
104 changes: 87 additions & 17 deletions mycore-base/src/main/java/org/mycore/common/xsl/MCRErrorListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@

import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.xml.utils.WrappedRuntimeException;

import net.sf.saxon.expr.instruct.TerminationException;
import net.sf.saxon.jaxp.StreamingTransformerImpl;
import net.sf.saxon.jaxp.TransformerImpl;
import net.sf.saxon.om.Item;
import net.sf.saxon.s9api.AbstractXsltTransformer;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.s9api.Message;
import net.sf.saxon.trans.XPathException;

/**
* @author Thomas Scheffler (yagee)
*
Expand All @@ -37,10 +47,42 @@ public final class MCRErrorListener implements ErrorListener {

private String lastMessage;

/**
* Creates a new {@link MCRErrorListener} and sets it to the given transformer.
* <p>
* For Saxon transformers, <code>xsl:message</code> output is logged, too.
*
* @param transformer the transformer to configure
* @return the error listener set to the transformer
*/
public static MCRErrorListener setTo(Transformer transformer) {
MCRErrorListener errorListener = new MCRErrorListener();
transformer.setErrorListener(errorListener);
AbstractXsltTransformer xsltTransformer = null;
if (transformer instanceof TransformerImpl saxonTransformer) {
xsltTransformer = saxonTransformer.getUnderlyingXsltTransformer();
} else if (transformer instanceof StreamingTransformerImpl saxonTransformer) {
xsltTransformer = saxonTransformer.getUnderlyingXsltTransformer();
}
if (xsltTransformer != null) {
// Saxon does not report xsl:message to the ErrorListener but writes it to System.err
xsltTransformer.setMessageHandler(MCRErrorListener::logMessage);
}
return errorListener;
}

public TransformerException getExceptionThrown() {
return exceptionThrown;
}

private static void logMessage(Message message) {
Location location = message.getLocation();
LOGGER.info(() -> location == null
? getMessageAndLocation(null, 0, 0, message.getStringValue())
: getMessageAndLocation(location.getSystemId(), location.getLineNumber(), location.getColumnNumber(),
message.getStringValue()));
}

private boolean triggerException(TransformerException e) {
if (exceptionThrown != null) {
return false;
Expand Down Expand Up @@ -90,6 +132,10 @@ public void fatalError(TransformerException exception) throws TransformerExcepti
&& stackTrace[0].getClassName().endsWith("ElemMessage")) {
LOGGER.debug("Original exception: ", unwrappedException);
unwrappedException = new TransformerException(lastMessage);
} else if (unwrappedException instanceof TerminationException) {
//Saxon: xsl:message with terminate="yes", the exception message does not contain the message text
LOGGER.debug("Original exception: ", unwrappedException);
unwrappedException = new TransformerException(getMyMessageAndLocation(unwrappedException));
}
if (triggerException(unwrappedException)) {
LOGGER.fatal("Exception while XSL transformation.", unwrappedException);
Expand All @@ -114,27 +160,51 @@ public static TransformerException unwrapException(TransformerException exceptio

public static String getMyMessageAndLocation(TransformerException exception) {
SourceLocator locator = exception.getLocator();
StringBuilder msg = new StringBuilder();
if (locator != null) {
String systemID = locator.getSystemId();
int line = locator.getLineNumber();
int col = locator.getColumnNumber();
if (systemID != null) {
msg.append("SystemID: ");
msg.append(systemID);
}
if (line != 0) {
msg.append(" [");
msg.append(line);
if (col != 0) {
msg.append(',');
msg.append(col);
String message = getMessage(exception);
if (locator == null) {
return getMessageAndLocation(null, 0, 0, message);
}
return getMessageAndLocation(locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber(),
message);
}

/**
* Returns the message of the given exception. For a Saxon exception caused by <code>xsl:message</code> with
* <code>terminate="yes"</code> this is the text of the message, if it is not empty.
*/
private static String getMessage(TransformerException exception) {
if (exception instanceof TerminationException terminationException
&& terminationException.getErrorObject() != null) {
try {
Item content = terminationException.getErrorObject().head();
String text = content == null ? null : content.getStringValue();
if (text != null && !text.isBlank()) {
return text;
}
msg.append(']');
} catch (XPathException e) {
LOGGER.debug("Could not get text of terminating xsl:message.", e);
}
}
return exception.getMessage();
}

private static String getMessageAndLocation(String systemID, int line, int col, String message) {
StringBuilder msg = new StringBuilder();
if (systemID != null) {
msg.append("SystemID: ");
msg.append(systemID);
}
if (line > 0) {
msg.append(" [");
msg.append(line);
if (col > 0) {
msg.append(',');
msg.append(col);
}
msg.append(']');
}
msg.append(": ");
msg.append(exception.getMessage());
msg.append(message);
return msg.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.mycore.common.MCRConstants;
import org.mycore.common.MCRUsageException;
import org.mycore.common.content.transformer.MCRXSLTransformer;
import org.mycore.common.xsl.MCRErrorListener;
import org.mycore.common.xsl.MCRXSLResourceHelper;
import org.mycore.common.xsl.uriresolver.MCRURIResolver;
import org.mycore.datamodel.common.MCRXMLMetadataManager;
Expand Down Expand Up @@ -188,6 +189,8 @@ public static Transformer getTransformer(String style, String defaultStyle, Map<
}
Transformer transformer = cache.get(style);
if (transformer != null) {
// fresh error listener, the old one keeps the exception of a previous transformation
MCRErrorListener.setTo(transformer);
return transformer;
}

Expand All @@ -202,6 +205,7 @@ public static Transformer getTransformer(String style, String defaultStyle, Map<
TransformerFactory transformerFactory = MCRXSLTransformer.createDefaultTransformerFactory();
transformerFactory.setURIResolver(MCRURIResolver.obtainInstance());
transformer = transformerFactory.newTransformer(new JDOMSource(element));
MCRErrorListener.setTo(transformer);
cache.put(style, transformer);
LOGGER.info("Loaded transformer from resource {} for style {}.", xslFilePath, style);
return transformer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,7 @@ private static void xslt(String objectId, String xslFilePath, boolean force) thr
xmlReader.setEntityResolver(MCREntityResolver.getInstance());
SAXSource styleSource = new SAXSource(xmlReader, style.getInputSource());
Transformer transformer = transformerFactory.newTransformer(styleSource);
MCRErrorListener.setTo(transformer);
for (Entry<String, String> property : MCRConfigurationBase.getAllPropertiesMap().entrySet()) {
transformer.setParameter(property.getKey(), property.getValue());
}
Expand Down
Loading