From aea44e391de8c3c38c782a883807ef9265c47c4e Mon Sep 17 00:00:00 2001 From: Brandon Bernard Date: Wed, 10 Aug 2022 18:47:27 -0500 Subject: [PATCH 1/2] - Fix several new issues with running locally via Visual Studio Code; has to remove ExtensionBundle from host.json to run as expected with Debugger successfully attaching!. - Cleaned up settings.json - Added more robust support for loading configuration values from Xml and implemented support for Accessibility value from ApacheFOP Xml Config as described in the documentation (or via Az Func configuration as a Serverless unique ability). --- .../.vscode/settings.json | 2 + apachefop-serverless-az-func/host.json | 10 +- .../apachefop/ApacheFopRenderer.java | 38 ++++++-- .../serverless/utils/XPathUtils.java | 94 +++++++++++++++++++ .../src/main/resources/apache-fop-config.xml | 3 + 5 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/utils/XPathUtils.java diff --git a/apachefop-serverless-az-func/.vscode/settings.json b/apachefop-serverless-az-func/.vscode/settings.json index 435acfd..c9a9b2b 100644 --- a/apachefop-serverless-az-func/.vscode/settings.json +++ b/apachefop-serverless-az-func/.vscode/settings.json @@ -7,7 +7,9 @@ "java.configuration.updateBuildConfiguration": "automatic", "maven.view": "hierarchical", "cSpell.words": [ + "apachefop", "Gzipped", + "Keepin", "xslfo" ] } \ No newline at end of file diff --git a/apachefop-serverless-az-func/host.json b/apachefop-serverless-az-func/host.json index 4ac8957..244d121 100644 --- a/apachefop-serverless-az-func/host.json +++ b/apachefop-serverless-az-func/host.json @@ -1,7 +1,7 @@ { - "version": "2.0", - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[1.*, 2.0.0)" - } + "version": "2.0"//, + // "extensionBundle": { + // "id": "Microsoft.Azure.Functions.ExtensionBundle", + // "version": "[1.*, 2.0.0)" + // } } \ No newline at end of file diff --git a/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/apachefop/ApacheFopRenderer.java b/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/apachefop/ApacheFopRenderer.java index 32b8ced..a77d910 100644 --- a/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/apachefop/ApacheFopRenderer.java +++ b/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/apachefop/ApacheFopRenderer.java @@ -3,14 +3,21 @@ import com.cajuncoding.apachefop.serverless.config.ApacheFopServerlessConfig; import com.cajuncoding.apachefop.serverless.config.ApacheFopServerlessConstants; import com.cajuncoding.apachefop.serverless.utils.ResourceUtils; +import com.cajuncoding.apachefop.serverless.utils.XPathUtils; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.fop.apps.*; import org.apache.fop.configuration.ConfigurationException; import org.apache.fop.configuration.DefaultConfigurationBuilder; +import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.*; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamSource; import java.io.*; +import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; @@ -87,29 +94,40 @@ public ApacheFopRenderResult renderPdfResult(String xslFOSource, boolean gzipEna } } + + protected synchronized void initApacheFopFactorySafely() { if(staticFopFactory == null) { var baseUri = new File(".").toURI(); var configFilePath = ApacheFopServerlessConstants.ConfigXmlResourceName; FopFactory newFopFactory = null; - try (var configStream = ResourceUtils.loadResourceAsStream(configFilePath);) { - if (configStream != null) { + try { + String configXmlText = ResourceUtils.loadResourceAsString(configFilePath); + if (!StringUtils.isBlank(configXmlText)) { //When Debugging log the full Configuration file... if(this.apacheFopConfig.isDebuggingEnabled()) { - var configFileXmlText =ResourceUtils.loadResourceAsString(configFilePath); - LogMessage("[DEBUG] ApacheFOP Configuration Xml:".concat(System.lineSeparator()).concat(configFileXmlText)); + LogMessage("[DEBUG] ApacheFOP Configuration Xml:".concat(System.lineSeparator()).concat(configXmlText)); } //Attempt to initialize with Configuration loaded from Configuration XML Resource file... - var cfgBuilder = new DefaultConfigurationBuilder(); - var cfg = cfgBuilder.build(configStream); - - var fopFactoryBuilder = new FopFactoryBuilder(baseUri, fopResourcesFileResolver).setConfiguration(cfg); - + //NOTE: FOP Factory requires a Stream so we have to initialize a new Stream for it to load from! + FopFactoryBuilder fopFactoryBuilder; + try(var configXmlStream = IOUtils.toInputStream(configXmlText, StandardCharsets.UTF_8)) { + var cfgBuilder = new DefaultConfigurationBuilder(); + var cfg = cfgBuilder.build(configXmlStream); + + fopFactoryBuilder = new FopFactoryBuilder(baseUri, fopResourcesFileResolver).setConfiguration(cfg); + } //Ensure Accessibility is programmatically set (default configuration is false)... - //fopFactoryBuilder.setAccessibility(this.apacheFopConfig.isAccessibilityPdfRenderingEnabled()); + //NOTE: There appears to be a bug in ApacheFOP code or documentation whereby it does not load the value from Xml as defined in the Docs! + // to work around this we read the value ourselves and also provide convenience support to simply set it in Azure Functions Configuration + // and if either configuration value is true then it will be enabled. + //NOTE: The XPathUtils is null safe so any issues in loading/parsing will simply result in null or default values... + var configXml = XPathUtils.fromXml(configXmlText); + var isAccessibilityEnabledInXmlConfig = configXml.evalXPathAsBoolean("//fop/accessibility", false); + fopFactoryBuilder.setAccessibility(this.apacheFopConfig.isAccessibilityPdfRenderingEnabled() || isAccessibilityEnabledInXmlConfig); newFopFactory = fopFactoryBuilder.build(); } diff --git a/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/utils/XPathUtils.java b/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/utils/XPathUtils.java new file mode 100644 index 0000000..e699898 --- /dev/null +++ b/apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/utils/XPathUtils.java @@ -0,0 +1,94 @@ +package com.cajuncoding.apachefop.serverless.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.io.IOUtils; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class XPathUtils { + + public static XPathUtils fromXml(String xmlContent) + { + try (var xmlContentStream = IOUtils.toInputStream(xmlContent, StandardCharsets.UTF_8)) { + return XPathUtils.fromXml(xmlContentStream); + } catch (IOException e) { + e.printStackTrace(); + } + + return new XPathUtils(null); + } + + public static XPathUtils fromXml(InputStream xmlContentStream) + { + Document xmlDocument = null; + try { + xmlDocument = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(xmlContentStream); + } catch (SAXException | IOException | ParserConfigurationException e) { + e.printStackTrace(); + } + + return new XPathUtils(xmlDocument); + } + + protected Document _xmlDoc; + + public XPathUtils(Document xmlDoc) + { + _xmlDoc = xmlDoc; + } + + public Document getXmlDocument() { + return _xmlDoc; + } + + public T evalXPath(String xpath, Class classType) throws XPathExpressionException + { + return evalXPathInternal(xpath, classType); + } + + public String evalXPathAsString(String xpath, String defaultIfNotFound) throws XPathExpressionException + { + try { + var result = evalXPathInternal(xpath, String.class); + return result != null ? result : defaultIfNotFound; + } catch (XPathExpressionException e) { + return defaultIfNotFound; + } + } + + public boolean evalXPathAsBoolean(String xpath, boolean defaultIfNotFound) + { + try { + var result = evalXPathInternal(xpath, Boolean.class); + return (boolean)(result != null ? result : defaultIfNotFound); + } catch (XPathExpressionException e) { + return defaultIfNotFound; + } + } + + public T evalXPathInternal(String xpathCommand, Class classType) throws XPathExpressionException + { + if(_xmlDoc == null) + return null; + + XPath xpath = XPathFactory.newInstance().newXPath(); + // //XPathExpression xpathExpression = xpathCompiler.compile(xpath); + // var result = xpathExpression.evaluate(_xmlDoc, returnType); + var result = xpath.evaluateExpression(xpathCommand, _xmlDoc, classType); + return result; + } +} + diff --git a/apachefop-serverless-az-func/src/main/resources/apache-fop-config.xml b/apachefop-serverless-az-func/src/main/resources/apache-fop-config.xml index d70703e..6f52b04 100644 --- a/apachefop-serverless-az-func/src/main/resources/apache-fop-config.xml +++ b/apachefop-serverless-az-func/src/main/resources/apache-fop-config.xml @@ -23,6 +23,9 @@ true + + false +