From 7260c030430ca575484ae0546863208004b1bbae Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Tue, 4 Apr 2023 16:46:52 +0100 Subject: [PATCH 01/14] [feat] Fetch/read saxon conf via broker pool Set up to configure Saxon with a license for PE/EE if we find such a license, assume at that point that PE/EE has been manually installed. WIP - not fully tested, UT or manually, and we need manual testing after installing EE to confirm that we can get the features we want. [fix] Remove licence file stuff, comment code There is no point in us pushing a licence file into Saxon from a different place, because as soon as we ask Saxon to load a configuration it looks for a licence file. We will just ask to dump a licence file where Saxon expects it by default. Comment the methods in the configuration holder. --- .../java/org/exist/storage/BrokerPool.java | 10 +- .../java/org/exist/util/Configuration.java | 14 ++ .../exist/util/SaxonConfigurationHolder.java | 218 ++++++++++++++++++ .../xquery/functions/fn/FnTransform.java | 4 +- .../functions/fn/transform/Options.java | 20 +- .../functions/fn/transform/Transform.java | 18 +- .../org/exist/config/SaxonLicenceTest.java | 33 +++ .../src/test/resources-filtered/conf.xml | 5 + .../xquery3/transform/fnTransform66.xqm | 2 +- 9 files changed, 302 insertions(+), 22 deletions(-) create mode 100644 exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java create mode 100644 exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java diff --git a/exist-core/src/main/java/org/exist/storage/BrokerPool.java b/exist-core/src/main/java/org/exist/storage/BrokerPool.java index e10256ea163..c07f253704a 100644 --- a/exist-core/src/main/java/org/exist/storage/BrokerPool.java +++ b/exist-core/src/main/java/org/exist/storage/BrokerPool.java @@ -379,7 +379,7 @@ public String getStatus() { * * One instance per-database, lazily initialised. */ - private AtomicLazyVal saxonConfig = new AtomicLazyVal<>(net.sf.saxon.Configuration::newConfiguration); + private SaxonConfigurationHolder saxonConfigurationHolder; /** * Creates and configures the database instance. @@ -420,6 +420,8 @@ public String getStatus() { this.pageSize = conf.getProperty(PROPERTY_PAGE_SIZE, DEFAULT_PAGE_SIZE); + this.saxonConfigurationHolder = SaxonConfigurationHolder.GetHolderForBroker(this); + //Configuration is valid, save it this.conf = conf; @@ -1926,7 +1928,11 @@ public void registerCollectionTrigger(final Class c } public net.sf.saxon.Configuration getSaxonConfiguration() { - return saxonConfig.get(); + return saxonConfigurationHolder.getConfiguration(); + } + + public net.sf.saxon.s9api.Processor getSaxonProcessor() { + return saxonConfigurationHolder.getProcessor(); } /** diff --git a/exist-core/src/main/java/org/exist/util/Configuration.java b/exist-core/src/main/java/org/exist/util/Configuration.java index c068f5e4e58..721d2f588ef 100644 --- a/exist-core/src/main/java/org/exist/util/Configuration.java +++ b/exist-core/src/main/java/org/exist/util/Configuration.java @@ -260,6 +260,12 @@ public Configuration(String configFilename, Optional existHomeDirname) thr configureTransformer((Element)transformers.item(0)); } + //saxon settings (most importantly license file for PE or EE features) + final NodeList saxon = doc.getElementsByTagName(SaxonConfigurationHolder.CONFIGURATION_ELEMENT_NAME); + if( saxon.getLength() > 0 ) { + configureSaxon((Element)saxon.item(0)); + } + //parser settings final NodeList parsers = doc.getElementsByTagName(HtmlToXmlParser.PARSER_ELEMENT_NAME); if(parsers.getLength() > 0) { @@ -538,6 +544,14 @@ private void configureXUpdate( Element xupdate ) throws NumberFormatException } } + private void configureSaxon( Element saxon ) + { + final String configurationFile = getConfigAttributeValue( saxon, + SaxonConfigurationHolder.CONFIGURATION_FILE_ATTRIBUTE); + if (configurationFile != null) { + config.put(SaxonConfigurationHolder.CONFIGURATION_FILE_PROPERTY, configurationFile); + } + } private void configureTransformer( Element transformer ) { diff --git a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java b/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java new file mode 100644 index 00000000000..4619a6c3e28 --- /dev/null +++ b/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java @@ -0,0 +1,218 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.util; + +import com.evolvedbinary.j8fu.lazy.AtomicLazyVal; +import net.sf.saxon.lib.Feature; +import net.sf.saxon.s9api.Processor; +import net.sf.saxon.trans.XPathException; +import org.exist.storage.BrokerPool; +import org.jline.utils.Log; + +import javax.xml.transform.stream.StreamSource; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public final class SaxonConfigurationHolder { + + public final static String CONFIGURATION_ELEMENT_NAME = "saxon"; + public final static String CONFIGURATION_FILE_ATTRIBUTE = "configuration-file"; + public final static String CONFIGURATION_FILE_PROPERTY = "saxon.configuration"; + private final static String DEFAULT_SAXON_CONFIG_FILE = "saxon-config.xml"; + + /** + * Maintain a separate, singleton Saxon configuration for each broker pool + */ + private static final Map perBroker = new HashMap<>(); + + private final BrokerPool brokerPool; + /** + * Load the Saxon configuration for this broker pool on demand + */ + private final AtomicLazyVal saxonConfiguration = new AtomicLazyVal<>(this::loadConfiguration); + + /** + * Create a Saxon processor from the Saxon configuration on demand + */ + private final AtomicLazyVal saxonProcessor = new AtomicLazyVal<>(this::createProcessor); + + /** + * Holds the Saxon configuration related to a broker pool + * Configuration elements are loaded on demand, + * initially this just holds a reference to the owning {@link BrokerPool} + * + * @param brokerPool which owns this Saxon configuration + */ + private SaxonConfigurationHolder(final BrokerPool brokerPool) { + this.brokerPool = brokerPool; + } + + /** + * Factory for the singleton Saxon configuration wrapper per broker pool + * @param brokerPool for which to fetch (and if necessary create) a Saxon configuration + * + * @return the associated Saxon configuration wrapper + */ + public synchronized static SaxonConfigurationHolder GetHolderForBroker(final BrokerPool brokerPool) { + if (!perBroker.containsKey(brokerPool)) { + perBroker.put(brokerPool, new SaxonConfigurationHolder(brokerPool)); + } + return perBroker.get(brokerPool); + } + + /** + * Get (lazy loading on first access) the Saxon API's {@link net.sf.saxon.Configuration} object + * + * @return Saxon internal configuration object + */ + public net.sf.saxon.Configuration getConfiguration() { + return saxonConfiguration.get(); + } + + /** + * Get (lazy loading on first access) the Saxon API's {@link Processor} through which Saxon operations + * such as transformation can be effected + * + * @return the Saxon {@link Processor} associated with the configuration. + */ + public Processor getProcessor() { + return saxonProcessor.get(); + } + + /** + * Create the Saxon {@link Processor} from the {@link net.sf.saxon.Configuration} when it is first needed + * + * @return a freshly created Saxon processor + */ + private Processor createProcessor() { + return new Processor(saxonConfiguration.get()); + } + + /** + * Load the Saxon {@link net.sf.saxon.Configuration} from a configuration file when it is first needed; + * if we cannot find a configuration file (and license) to give to Saxon, it may still be able to find + * something. + * + * @return a freshly loaded Saxon configuration + */ + private net.sf.saxon.Configuration loadConfiguration() { + + final var existConfiguration = brokerPool.getConfiguration(); + final var saxonConfigFile = getSaxonConfigFile(existConfiguration); + Optional saxonConfiguration = Optional.empty(); + if (saxonConfigFile.isPresent()) { + try { + saxonConfiguration = Optional.of(net.sf.saxon.Configuration.readConfiguration( + new StreamSource(new FileInputStream(saxonConfigFile.get())))); + } catch (XPathException | FileNotFoundException e) { + Log.warn("Saxon could not read the configuration file: " + saxonConfigFile.get() + + ", with error: " + e.getMessage(), e); + } catch (RuntimeException runtimeException) { + if (runtimeException.getCause() instanceof ClassNotFoundException e) { + Log.warn("Saxon could not honour the configuration file: " + saxonConfigFile.get() + + ", with class not found error: " + e.getMessage(), e); + } else { + throw runtimeException; + } + } + } + if (saxonConfiguration.isEmpty()) { + saxonConfiguration = Optional.of(net.sf.saxon.Configuration.newConfiguration()); + } + + if (saxonConfigFile.isEmpty()) { + Log.warn("eXist could not find any Saxon configuration:\n" + + "No Saxon configuration file in configuration item " + CONFIGURATION_FILE_PROPERTY + "\n" + + "No default eXist Saxon configuration file " + DEFAULT_SAXON_CONFIG_FILE); + } + + saxonConfiguration.ifPresent(net.sf.saxon.Configuration::displayLicenseMessage); + saxonConfiguration.ifPresent(configuration -> { + final var sb = new StringBuilder(); + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.SCHEMA_VALIDATION)) { + sb.append(" SCHEMA_VALIDATION"); + } + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.ENTERPRISE_XSLT)) { + sb.append(" ENTERPRISE_XSLT"); + } + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.ENTERPRISE_XQUERY)) { + sb.append(" ENTERPRISE_XQUERY"); + } + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.PROFESSIONAL_EDITION)) { + sb.append(" PROFESSIONAL_EDITION"); + } + if (sb.length() == 0) { + Log.info("Saxon - no licensed features reported."); + } else { + Log.info("Saxon - licensed features are" + sb + "."); + } + }); + + return saxonConfiguration.get(); + } + + /** + * Resolve a possibly relative configuration file; + * if it is relative, it is relative to the current exist configuration (conf.xml) + * + * @param existConfiguration configuration to which this file may be relative + * @param filename the file we are trying to resolve + * @return the input file, if it is absolute. a file relative to conf.xml, if the input file is relative + */ + private static File resolveConfigurationFile(final Configuration existConfiguration, final String filename) { + final var configurationFile = new File(filename); + if (configurationFile.isAbsolute()) { + return configurationFile; + } + final var configPath = existConfiguration.getConfigFilePath(); + if (configPath.isPresent()) { + final var resolvedPath = configPath.get().getParent().resolve(configurationFile.toPath()); + return resolvedPath.toFile(); + } + return configurationFile; + } + + private static Optional getSaxonConfigFile(final Configuration existConfiguration) { + + if (existConfiguration.getProperty(CONFIGURATION_FILE_PROPERTY) instanceof String saxonConfigurationFile) { + final var configurationFile = resolveConfigurationFile(existConfiguration, saxonConfigurationFile); + if (configurationFile.canRead()) { + return Optional.of(configurationFile); + } else { + Log.warn("Configuration item " + CONFIGURATION_FILE_PROPERTY + " : " + configurationFile + + " does not refer to a readable file. Continuing search for Saxon configuration."); + } + } + + final var configurationFile = resolveConfigurationFile(existConfiguration, DEFAULT_SAXON_CONFIG_FILE); + if (configurationFile.canRead()) { + return Optional.of(configurationFile); + } + + return Optional.empty(); + } +} diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnTransform.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnTransform.java index e44fd80605d..e3b1179c705 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnTransform.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnTransform.java @@ -57,10 +57,12 @@ public class FnTransform extends BasicFunction { param("options", Type.MAP, "The inputs to the transformation are supplied in the form of a map") ); - private Transform transform = new Transform(context, this); + private Transform transform; public FnTransform(final XQueryContext context, final FunctionSignature signature) { + super(context, signature); + transform = new Transform(context, this); } @Override diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java index c439c08cd2b..20faf279f55 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java @@ -69,8 +69,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.exist.Namespaces.XSL_NS; import static org.exist.xquery.functions.fn.transform.Options.Option.*; -import static org.exist.xquery.functions.fn.transform.Transform.SAXON_CONFIGURATION; -import static org.exist.xquery.functions.fn.transform.Transform.toSaxon; /** * Read options into class values in a single place. @@ -126,10 +124,15 @@ class Options { private final XQueryContext context; private final FnTransform fnTransform; + private final Convert.ToSaxon toSaxon; - Options(final XQueryContext context, final FnTransform fnTransform, final MapType options) throws XPathException { + private final SystemProperties systemProperties; + + Options(final XQueryContext context, final FnTransform fnTransform, final Convert.ToSaxon toSaxon, final MapType options) throws XPathException { this.context = context; this.fnTransform = fnTransform; + this.toSaxon = toSaxon; + this.systemProperties = new SystemProperties(context); xsltSource = getStylesheet(options); @@ -282,7 +285,7 @@ private void validateRequestedProperties(final MapType requestedProperties) thro " is not a " + Type.getTypeName(Type.STRING) + " or a " + Type.getTypeName(Type.BOOLEAN)); } - final String actualPropertyValue = SystemProperties.get(((QNameValue) key).getQName()); + final String actualPropertyValue = systemProperties.get(((QNameValue) key).getQName()); if (!actualPropertyValue.equalsIgnoreCase(requiredPropertyValue)) { throw new XPathException(ErrorCodes.FOXT0001, "The XSLT processor cannot provide the requested-property: " + key + @@ -651,13 +654,14 @@ public Reader getReader() { static class SystemProperties { - private SystemProperties() { - super(); + private SystemProperties(XQueryContext context) { + final var saxonConfiguration = context.getBroker().getBrokerPool().getSaxonConfiguration(); + this.retainedStaticContext = new RetainedStaticContext(saxonConfiguration); } - private static final RetainedStaticContext retainedStaticContext = new RetainedStaticContext(SAXON_CONFIGURATION); + private final RetainedStaticContext retainedStaticContext; - static String get(org.exist.dom.QName qName) { + String get(org.exist.dom.QName qName) { return SystemProperty.getProperty(qName.getNamespaceURI(), qName.getLocalPart(), retainedStaticContext); } } 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 1e6ca3a0de4..834edc08ab2 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 @@ -25,7 +25,6 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import io.lacuna.bifurcan.IEntry; -import net.sf.saxon.Configuration; import net.sf.saxon.om.StructuredQName; import net.sf.saxon.s9api.*; import net.sf.saxon.serialize.SerializationProperties; @@ -35,6 +34,7 @@ import org.apache.logging.log4j.Logger; import org.exist.dom.QName; import org.exist.dom.memtree.DocumentImpl; +import org.exist.storage.BrokerPool; import org.exist.util.Holder; import org.exist.xquery.ErrorCodes; import org.exist.xquery.XPathException; @@ -85,14 +85,10 @@ public class Transform { private static final Logger LOGGER = LogManager.getLogger(org.exist.xquery.functions.fn.transform.Transform.class); private static final org.exist.xquery.functions.fn.transform.Transform.ErrorListenerLog4jAdapter ERROR_LISTENER = new Transform.ErrorListenerLog4jAdapter(Transform.LOGGER); - //TODO(AR) if you want Saxon-EE features we need to set those in the Configuration - static final Configuration SAXON_CONFIGURATION = new Configuration(); - private static final Processor SAXON_PROCESSOR = new Processor(org.exist.xquery.functions.fn.transform.Transform.SAXON_CONFIGURATION); - - static final Convert.ToSaxon toSaxon = new Convert.ToSaxon() { + final Convert.ToSaxon toSaxon = new Convert.ToSaxon() { @Override DocumentBuilder newDocumentBuilder() { - return SAXON_PROCESSOR.newDocumentBuilder(); + return context.getBroker().getBrokerPool().getSaxonProcessor().newDocumentBuilder(); } }; @@ -104,6 +100,7 @@ DocumentBuilder newDocumentBuilder() { private final XQueryContext context; private final FnTransform fnTransform; + public Transform(final XQueryContext context, final FnTransform fnTransform) { this.context = context; this.fnTransform = fnTransform; @@ -111,7 +108,7 @@ public Transform(final XQueryContext context, final FnTransform fnTransform) { public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException { - final Options options = new Options(context, fnTransform, (MapType) args[0].itemAt(0)); + final Options options = new Options(context, fnTransform, toSaxon, (MapType) args[0].itemAt(0)); //TODO(AR) Saxon recommends to use a StreamSource or SAXSource instead of DOMSource for performance final Optional sourceNode = Transform.getSourceNode(options.sourceNode, context.getBaseURI()); @@ -183,7 +180,8 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro document = node.getOwnerDocument(); source = new DOMSource(document); } - final DocumentBuilder sourceBuilder = Transform.SAXON_PROCESSOR.newDocumentBuilder(); + final var brokerPool = context.getBroker().getBrokerPool(); + final DocumentBuilder sourceBuilder = brokerPool.getSaxonProcessor().newDocumentBuilder(); final XdmNode xdmNode = sourceBuilder.build(source); xslt30Transformer.setGlobalContextItem(xdmNode); } else { @@ -205,7 +203,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro private XsltExecutable compileExecutable(final Options options) throws XPathException { - final XsltCompiler xsltCompiler = org.exist.xquery.functions.fn.transform.Transform.SAXON_PROCESSOR.newXsltCompiler(); + final XsltCompiler xsltCompiler = context.getBroker().getBrokerPool().getSaxonProcessor().newXsltCompiler(); final SingleRequestErrorListener errorListener = new SingleRequestErrorListener(Transform.ERROR_LISTENER); xsltCompiler.setErrorListener(errorListener); diff --git a/exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java b/exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java new file mode 100644 index 00000000000..d517e171fb2 --- /dev/null +++ b/exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java @@ -0,0 +1,33 @@ +package org.exist.config; + +import org.exist.test.ExistEmbeddedServer; +import org.exist.util.SaxonConfigurationHolder; +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SaxonLicenceTest { + + @ClassRule + public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); + + @Test + public void configFromBroker() { + final var brokerPool = existEmbeddedServer.getBrokerPool(); + + final var existConfiguration = brokerPool.getConfiguration(); + assertThat(existConfiguration.getProperty("saxon.configuration")).isEqualTo("saxon-config-for-exist.xml"); + assertThat(existConfiguration.getProperty("saxon.license")).isEqualTo("saxon-license-for-exist.lic"); + + final var saxonConfigurationHolder = SaxonConfigurationHolder.GetHolderForBroker(brokerPool); + final var saxonConfiguration = saxonConfigurationHolder.getConfiguration(); + + final var saxonProcessor = saxonConfigurationHolder.getProcessor(); + + //TODO (AP) - brokerPool.getSaxonConfiguration() needs to use SaxonConfigurationHolder.GetHolderForBroker(brokerPool) + final var saxonConfiguration2 = brokerPool.getSaxonConfiguration(); + assertThat(saxonConfiguration2).isSameAs(saxonConfiguration); + + } +} diff --git a/exist-core/src/test/resources-filtered/conf.xml b/exist-core/src/test/resources-filtered/conf.xml index 7ccbc595154..b1e3ad9b67d 100644 --- a/exist-core/src/test/resources-filtered/conf.xml +++ b/exist-core/src/test/resources-filtered/conf.xml @@ -931,4 +931,9 @@ --> + + + diff --git a/exist-core/src/test/xquery/xquery3/transform/fnTransform66.xqm b/exist-core/src/test/xquery/xquery3/transform/fnTransform66.xqm index 34a06398d94..75715d93e31 100644 --- a/exist-core/src/test/xquery/xquery3/transform/fnTransform66.xqm +++ b/exist-core/src/test/xquery/xquery3/transform/fnTransform66.xqm @@ -63,6 +63,6 @@ function testTransform:transform-66-black() { "initial-template": fn:QName('','main'), "delivery-format" : "serialized", "serialization-params": map{'suppress-indentation': (QName('http://www.w3.org/fots/fn/transform/myfunctions','c'), QName('', 'c'))}}) - return matches($result("output"), ">\s+black\s+<") + return fn:matches($result("output"), ">\s+black\s+<") }; From cb0519474e86b6cfc9e190223b8403bab6ca013b Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Thu, 6 Apr 2023 08:56:53 +0100 Subject: [PATCH 02/14] [fix] Link to and polish the placeholder saxon config. The saxon-config.xml file we supply does nothing, it just acts as a placeholder for a fully-realised saxon-config.xml when a licensed Saxon edition is installed. --- .../org/exist/util/SaxonConfigurationHolder.java | 2 +- exist-distribution/src/main/config/conf.xml | 6 ++++++ .../src/main/config/saxon-config.xml | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 exist-distribution/src/main/config/saxon-config.xml diff --git a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java b/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java index 4619a6c3e28..01c7ccf2bb2 100644 --- a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java +++ b/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java @@ -134,7 +134,7 @@ private net.sf.saxon.Configuration loadConfiguration() { } catch (RuntimeException runtimeException) { if (runtimeException.getCause() instanceof ClassNotFoundException e) { Log.warn("Saxon could not honour the configuration file: " + saxonConfigFile.get() + - ", with class not found error: " + e.getMessage(), e); + ", with class not found error: " + e.getMessage() + ". You may need to install the SaxonPE or SaxonEE JAR in eXist."); } else { throw runtimeException; } diff --git a/exist-distribution/src/main/config/conf.xml b/exist-distribution/src/main/config/conf.xml index 5de51f24943..f646a11785f 100644 --- a/exist-distribution/src/main/config/conf.xml +++ b/exist-distribution/src/main/config/conf.xml @@ -1104,4 +1104,10 @@ --> + + + + diff --git a/exist-distribution/src/main/config/saxon-config.xml b/exist-distribution/src/main/config/saxon-config.xml new file mode 100644 index 00000000000..332f95f8c20 --- /dev/null +++ b/exist-distribution/src/main/config/saxon-config.xml @@ -0,0 +1,14 @@ + + + From d1ea717f01f9938f2d53825ac8f0f9c7fcbc03b6 Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Thu, 6 Apr 2023 10:17:04 +0100 Subject: [PATCH 03/14] [test] Document and validate HE to EE upgrade procedure. Instructions in markdown for how to achieve this. --- exist-distribution/Saxon-EE.md | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 exist-distribution/Saxon-EE.md diff --git a/exist-distribution/Saxon-EE.md b/exist-distribution/Saxon-EE.md new file mode 100644 index 00000000000..01c6140a3f6 --- /dev/null +++ b/exist-distribution/Saxon-EE.md @@ -0,0 +1,77 @@ +# How to use Saxon PE or EE + +eXistDB uses an embedded copy of Saxon HE as an XSLT transformer. +If the user wishes to take advantage of the extra features of Saxon PE or Saxon EE, +and has licensed that software, these instructions will enable them to use the extra +Saxon features from within eXist, for example when invoking `fn:transform()`. + +In the instructions below, `$EXIST_HOME` refers to the root directory into which eXist has been installed. +This is the directory which is prompted for (and or defaulted) by the eXist installer. + +## Replace the Saxon JAR file + + * Replace the existing `$EXIST_HOME/lib/Saxon-HE-9.9.1-8.jar` with `saxon9ee.jar` fetched by downloading `https://www.saxonica.com/download/SaxonEE9-9-1-8J.zip` and unzipping. + * At present only Saxon EE (or PE) version 9.9.1.8 is supported. + * Confirm the JAR is present at `$EXIST_HOME/lib/saxon9ee.jar` + +## Update configuration + +In both `$EXIST_HOME/etc/client.xml` and `$EXIST_HOME/etc/startup.xml` replace the dependency +``` + + net.sf.saxon + Saxon-HE + 9.9.1-8 + Saxon-HE-9.9.1-8.jar + +``` +with a dependency on the JAR you just installed +``` + + net.sf.saxon + Saxon-EE + 9.9.1-8 + saxon9ee.jar + +``` + +## Add your license file + +Place the `saxon-license.lic` file in `$EXIST_HOME/lib` + +## Update the config file + +At a mimimun, you will need to replace +``` + + +``` +with +``` + + +``` +in the existing `$EXIST_HOME/etc/saxon-config.xml` + +You may have specific configuration which you wish to set up. + +Example instantiated configuration files for the enterprise edition can be found by visiting + https://saxonica.com/html/download/download_page.html + +For documentation on the contents of a Saxon configuration file, see + http://www.saxonica.com/html/documentation/configuration/configuration-file/index.html + +## (Re)start eXist + +Using the normal control mechanism. + +The Saxon configuration is loaded lazily. So you will need to cause this to happen by (for example) calling `fn:transform` from xquery in eXide. + +You should then see a log message in `$EXIST_HOME/logs/exist.log` similar to this +``` +Saxon - licensed features are SCHEMA_VALIDATION ENTERPRISE_XSLT ENTERPRISE_XQUERY PROFESSIONAL_EDITION. +``` From 77b0c1afe26fafa1da186c40db23fe68c4e6cdca Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Thu, 6 Apr 2023 10:37:00 +0100 Subject: [PATCH 04/14] [refactor] Rename, add licence header, remove license testing Saxon config tests that we find the saxon config file; the part about looking for a license file is not implemented. --- ...nLicenceTest.java => SaxonConfigTest.java} | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) rename exist-core/src/test/java/org/exist/config/{SaxonLicenceTest.java => SaxonConfigTest.java} (53%) diff --git a/exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java b/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java similarity index 53% rename from exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java rename to exist-core/src/test/java/org/exist/config/SaxonConfigTest.java index d517e171fb2..d1ec068694f 100644 --- a/exist-core/src/test/java/org/exist/config/SaxonLicenceTest.java +++ b/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java @@ -1,3 +1,25 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + package org.exist.config; import org.exist.test.ExistEmbeddedServer; @@ -7,7 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class SaxonLicenceTest { +public class SaxonConfigTest { @ClassRule public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true); @@ -17,8 +39,7 @@ public void configFromBroker() { final var brokerPool = existEmbeddedServer.getBrokerPool(); final var existConfiguration = brokerPool.getConfiguration(); - assertThat(existConfiguration.getProperty("saxon.configuration")).isEqualTo("saxon-config-for-exist.xml"); - assertThat(existConfiguration.getProperty("saxon.license")).isEqualTo("saxon-license-for-exist.lic"); + assertThat(existConfiguration.getProperty("saxon.configuration")).isEqualTo("saxon-config.xml"); final var saxonConfigurationHolder = SaxonConfigurationHolder.GetHolderForBroker(brokerPool); final var saxonConfiguration = saxonConfigurationHolder.getConfiguration(); From 866b63ddf5faa344e63044e71c1760ffa0749885 Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Thu, 6 Apr 2023 14:54:14 +0100 Subject: [PATCH 05/14] [refactor] Review feedback (human and automatic) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De-genericize static names Don’t capitalize static method Remove unused imports Unpick method for complexity metric Re-order some qualifiers Make markdown conform to checker rules --- .../java/org/exist/storage/BrokerPool.java | 3 +- .../java/org/exist/util/Configuration.java | 6 +- .../exist/util/SaxonConfigurationHolder.java | 105 +++++++++--------- .../functions/fn/transform/Options.java | 4 +- .../functions/fn/transform/Transform.java | 1 - .../org/exist/config/SaxonConfigTest.java | 6 +- exist-distribution/Saxon-EE.md | 14 +-- 7 files changed, 72 insertions(+), 67 deletions(-) diff --git a/exist-core/src/main/java/org/exist/storage/BrokerPool.java b/exist-core/src/main/java/org/exist/storage/BrokerPool.java index c07f253704a..af10028e1ac 100644 --- a/exist-core/src/main/java/org/exist/storage/BrokerPool.java +++ b/exist-core/src/main/java/org/exist/storage/BrokerPool.java @@ -23,7 +23,6 @@ import com.evolvedbinary.j8fu.fsm.AtomicFSM; import com.evolvedbinary.j8fu.fsm.FSM; -import com.evolvedbinary.j8fu.lazy.AtomicLazyVal; import net.jcip.annotations.ThreadSafe; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -420,7 +419,7 @@ public String getStatus() { this.pageSize = conf.getProperty(PROPERTY_PAGE_SIZE, DEFAULT_PAGE_SIZE); - this.saxonConfigurationHolder = SaxonConfigurationHolder.GetHolderForBroker(this); + this.saxonConfigurationHolder = SaxonConfigurationHolder.getHolderForBroker(this); //Configuration is valid, save it this.conf = conf; diff --git a/exist-core/src/main/java/org/exist/util/Configuration.java b/exist-core/src/main/java/org/exist/util/Configuration.java index 721d2f588ef..7df8a9e2e70 100644 --- a/exist-core/src/main/java/org/exist/util/Configuration.java +++ b/exist-core/src/main/java/org/exist/util/Configuration.java @@ -261,7 +261,7 @@ public Configuration(String configFilename, Optional existHomeDirname) thr } //saxon settings (most importantly license file for PE or EE features) - final NodeList saxon = doc.getElementsByTagName(SaxonConfigurationHolder.CONFIGURATION_ELEMENT_NAME); + final NodeList saxon = doc.getElementsByTagName(SaxonConfigurationHolder.SAXON_CONFIGURATION_ELEMENT_NAME); if( saxon.getLength() > 0 ) { configureSaxon((Element)saxon.item(0)); } @@ -547,9 +547,9 @@ private void configureXUpdate( Element xupdate ) throws NumberFormatException private void configureSaxon( Element saxon ) { final String configurationFile = getConfigAttributeValue( saxon, - SaxonConfigurationHolder.CONFIGURATION_FILE_ATTRIBUTE); + SaxonConfigurationHolder.SAXON_CONFIGURATION_FILE_ATTRIBUTE); if (configurationFile != null) { - config.put(SaxonConfigurationHolder.CONFIGURATION_FILE_PROPERTY, configurationFile); + config.put(SaxonConfigurationHolder.SAXON_CONFIGURATION_FILE_PROPERTY, configurationFile); } } diff --git a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java b/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java index 01c7ccf2bb2..13394777b7a 100644 --- a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java +++ b/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java @@ -22,7 +22,6 @@ package org.exist.util; import com.evolvedbinary.j8fu.lazy.AtomicLazyVal; -import net.sf.saxon.lib.Feature; import net.sf.saxon.s9api.Processor; import net.sf.saxon.trans.XPathException; import org.exist.storage.BrokerPool; @@ -32,17 +31,16 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; public final class SaxonConfigurationHolder { - public final static String CONFIGURATION_ELEMENT_NAME = "saxon"; - public final static String CONFIGURATION_FILE_ATTRIBUTE = "configuration-file"; - public final static String CONFIGURATION_FILE_PROPERTY = "saxon.configuration"; - private final static String DEFAULT_SAXON_CONFIG_FILE = "saxon-config.xml"; + public static final String SAXON_CONFIGURATION_ELEMENT_NAME = "saxon"; + public static final String SAXON_CONFIGURATION_FILE_ATTRIBUTE = "configuration-file"; + public static final String SAXON_CONFIGURATION_FILE_PROPERTY = "saxon.configuration"; + private static final String SAXON_DEFAULT_SAXON_CONFIG_FILE = "saxon-config.xml"; /** * Maintain a separate, singleton Saxon configuration for each broker pool @@ -77,11 +75,8 @@ private SaxonConfigurationHolder(final BrokerPool brokerPool) { * * @return the associated Saxon configuration wrapper */ - public synchronized static SaxonConfigurationHolder GetHolderForBroker(final BrokerPool brokerPool) { - if (!perBroker.containsKey(brokerPool)) { - perBroker.put(brokerPool, new SaxonConfigurationHolder(brokerPool)); - } - return perBroker.get(brokerPool); + public static synchronized SaxonConfigurationHolder getHolderForBroker(final BrokerPool brokerPool) { + return perBroker.computeIfAbsent(brokerPool, SaxonConfigurationHolder::new); } /** @@ -125,20 +120,7 @@ private net.sf.saxon.Configuration loadConfiguration() { final var saxonConfigFile = getSaxonConfigFile(existConfiguration); Optional saxonConfiguration = Optional.empty(); if (saxonConfigFile.isPresent()) { - try { - saxonConfiguration = Optional.of(net.sf.saxon.Configuration.readConfiguration( - new StreamSource(new FileInputStream(saxonConfigFile.get())))); - } catch (XPathException | FileNotFoundException e) { - Log.warn("Saxon could not read the configuration file: " + saxonConfigFile.get() + - ", with error: " + e.getMessage(), e); - } catch (RuntimeException runtimeException) { - if (runtimeException.getCause() instanceof ClassNotFoundException e) { - Log.warn("Saxon could not honour the configuration file: " + saxonConfigFile.get() + - ", with class not found error: " + e.getMessage() + ". You may need to install the SaxonPE or SaxonEE JAR in eXist."); - } else { - throw runtimeException; - } - } + saxonConfiguration = readSaxonConfigurationFile(saxonConfigFile.get()); } if (saxonConfiguration.isEmpty()) { saxonConfiguration = Optional.of(net.sf.saxon.Configuration.newConfiguration()); @@ -146,33 +128,56 @@ private net.sf.saxon.Configuration loadConfiguration() { if (saxonConfigFile.isEmpty()) { Log.warn("eXist could not find any Saxon configuration:\n" + - "No Saxon configuration file in configuration item " + CONFIGURATION_FILE_PROPERTY + "\n" + - "No default eXist Saxon configuration file " + DEFAULT_SAXON_CONFIG_FILE); + "No Saxon configuration file in configuration item " + SAXON_CONFIGURATION_FILE_PROPERTY + "\n" + + "No default eXist Saxon configuration file " + SAXON_DEFAULT_SAXON_CONFIG_FILE); } - saxonConfiguration.ifPresent(net.sf.saxon.Configuration::displayLicenseMessage); - saxonConfiguration.ifPresent(configuration -> { - final var sb = new StringBuilder(); - if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.SCHEMA_VALIDATION)) { - sb.append(" SCHEMA_VALIDATION"); - } - if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.ENTERPRISE_XSLT)) { - sb.append(" ENTERPRISE_XSLT"); - } - if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.ENTERPRISE_XQUERY)) { - sb.append(" ENTERPRISE_XQUERY"); - } - if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.PROFESSIONAL_EDITION)) { - sb.append(" PROFESSIONAL_EDITION"); - } - if (sb.length() == 0) { - Log.info("Saxon - no licensed features reported."); + saxonConfiguration.ifPresent(SaxonConfigurationHolder::reportLicensedFeatures); + + return saxonConfiguration.get(); + } + + static private Optional readSaxonConfigurationFile(final File saxonConfigFile) { + + try { + return Optional.of(net.sf.saxon.Configuration.readConfiguration( + new StreamSource(new FileInputStream(saxonConfigFile)))); + } catch (XPathException | FileNotFoundException e) { + Log.warn("Saxon could not read the configuration file: " + saxonConfigFile + + ", with error: " + e.getMessage(), e); + } catch (RuntimeException runtimeException) { + if (runtimeException.getCause() instanceof ClassNotFoundException e) { + Log.warn("Saxon could not honour the configuration file: " + saxonConfigFile + + ", with class not found error: " + e.getMessage() + ". You may need to install the SaxonPE or SaxonEE JAR in eXist."); } else { - Log.info("Saxon - licensed features are" + sb + "."); + throw runtimeException; } - }); + } - return saxonConfiguration.get(); + return Optional.empty(); + } + + static private void reportLicensedFeatures(final net.sf.saxon.Configuration configuration) { + configuration.displayLicenseMessage(); + + final var sb = new StringBuilder(); + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.SCHEMA_VALIDATION)) { + sb.append(" SCHEMA_VALIDATION"); + } + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.ENTERPRISE_XSLT)) { + sb.append(" ENTERPRISE_XSLT"); + } + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.ENTERPRISE_XQUERY)) { + sb.append(" ENTERPRISE_XQUERY"); + } + if (configuration.isLicensedFeature(net.sf.saxon.Configuration.LicenseFeature.PROFESSIONAL_EDITION)) { + sb.append(" PROFESSIONAL_EDITION"); + } + if (sb.length() == 0) { + Log.info("Saxon - no licensed features reported."); + } else { + Log.info("Saxon - licensed features are" + sb + "."); + } } /** @@ -198,17 +203,17 @@ private static File resolveConfigurationFile(final Configuration existConfigurat private static Optional getSaxonConfigFile(final Configuration existConfiguration) { - if (existConfiguration.getProperty(CONFIGURATION_FILE_PROPERTY) instanceof String saxonConfigurationFile) { + if (existConfiguration.getProperty(SAXON_CONFIGURATION_FILE_PROPERTY) instanceof String saxonConfigurationFile) { final var configurationFile = resolveConfigurationFile(existConfiguration, saxonConfigurationFile); if (configurationFile.canRead()) { return Optional.of(configurationFile); } else { - Log.warn("Configuration item " + CONFIGURATION_FILE_PROPERTY + " : " + configurationFile + + Log.warn("Configuration item " + SAXON_CONFIGURATION_FILE_PROPERTY + " : " + configurationFile + " does not refer to a readable file. Continuing search for Saxon configuration."); } } - final var configurationFile = resolveConfigurationFile(existConfiguration, DEFAULT_SAXON_CONFIG_FILE); + final var configurationFile = resolveConfigurationFile(existConfiguration, SAXON_DEFAULT_SAXON_CONFIG_FILE); if (configurationFile.canRead()) { return Optional.of(configurationFile); } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java index 20faf279f55..1008ae30c1b 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java @@ -654,13 +654,13 @@ public Reader getReader() { static class SystemProperties { + private final RetainedStaticContext retainedStaticContext; + private SystemProperties(XQueryContext context) { final var saxonConfiguration = context.getBroker().getBrokerPool().getSaxonConfiguration(); this.retainedStaticContext = new RetainedStaticContext(saxonConfiguration); } - private final RetainedStaticContext retainedStaticContext; - String get(org.exist.dom.QName qName) { return SystemProperty.getProperty(qName.getNamespaceURI(), qName.getLocalPart(), retainedStaticContext); } 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 834edc08ab2..8689b392d3c 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 @@ -34,7 +34,6 @@ import org.apache.logging.log4j.Logger; import org.exist.dom.QName; import org.exist.dom.memtree.DocumentImpl; -import org.exist.storage.BrokerPool; import org.exist.util.Holder; import org.exist.xquery.ErrorCodes; import org.exist.xquery.XPathException; diff --git a/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java b/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java index d1ec068694f..f7ad8eb1e31 100644 --- a/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java +++ b/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java @@ -41,12 +41,14 @@ public void configFromBroker() { final var existConfiguration = brokerPool.getConfiguration(); assertThat(existConfiguration.getProperty("saxon.configuration")).isEqualTo("saxon-config.xml"); - final var saxonConfigurationHolder = SaxonConfigurationHolder.GetHolderForBroker(brokerPool); + final var saxonConfigurationHolder = SaxonConfigurationHolder.getHolderForBroker(brokerPool); final var saxonConfiguration = saxonConfigurationHolder.getConfiguration(); + // There is no way to install EE at the test/build phase. + // Sanity check is to confirm this does indeed return "HE" (Home Edition). final var saxonProcessor = saxonConfigurationHolder.getProcessor(); + assertThat(saxonProcessor.getSaxonEdition()).isEqualTo("HE"); - //TODO (AP) - brokerPool.getSaxonConfiguration() needs to use SaxonConfigurationHolder.GetHolderForBroker(brokerPool) final var saxonConfiguration2 = brokerPool.getSaxonConfiguration(); assertThat(saxonConfiguration2).isSameAs(saxonConfiguration); diff --git a/exist-distribution/Saxon-EE.md b/exist-distribution/Saxon-EE.md index 01c6140a3f6..781f4aa7fbe 100644 --- a/exist-distribution/Saxon-EE.md +++ b/exist-distribution/Saxon-EE.md @@ -10,14 +10,14 @@ This is the directory which is prompted for (and or defaulted) by the eXist inst ## Replace the Saxon JAR file - * Replace the existing `$EXIST_HOME/lib/Saxon-HE-9.9.1-8.jar` with `saxon9ee.jar` fetched by downloading `https://www.saxonica.com/download/SaxonEE9-9-1-8J.zip` and unzipping. - * At present only Saxon EE (or PE) version 9.9.1.8 is supported. - * Confirm the JAR is present at `$EXIST_HOME/lib/saxon9ee.jar` +* Replace the existing `$EXIST_HOME/lib/Saxon-HE-9.9.1-8.jar` with `saxon9ee.jar` fetched by downloading `https://www.saxonica.com/download/SaxonEE9-9-1-8J.zip` and unzipping. +* At present only Saxon EE (or PE) version 9.9.1.8 is supported. +* Confirm the JAR is present at `$EXIST_HOME/lib/saxon9ee.jar` ## Update configuration In both `$EXIST_HOME/etc/client.xml` and `$EXIST_HOME/etc/startup.xml` replace the dependency -``` +```xml net.sf.saxon Saxon-HE @@ -26,7 +26,7 @@ In both `$EXIST_HOME/etc/client.xml` and `$EXIST_HOME/etc/startup.xml` replace t ``` with a dependency on the JAR you just installed -``` +```xml net.sf.saxon Saxon-EE @@ -42,14 +42,14 @@ Place the `saxon-license.lic` file in `$EXIST_HOME/lib` ## Update the config file At a mimimun, you will need to replace -``` +```xml ``` with -``` +```xml From c3c82de23ebbaa6793af2de7bead6ed4752981eb Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Mon, 24 Apr 2023 08:59:58 +0100 Subject: [PATCH 06/14] [refactor] how broker pool holds saxon config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t need a map of broker pool to saxon config, I’m not sure why we made it like that in the first place. Broker pool just needs a (lazy) reference to its own saxon configuration (wrapper). --- .../java/org/exist/storage/BrokerPool.java | 17 ++--- .../java/org/exist/util/Configuration.java | 6 +- ...ionHolder.java => SaxonConfiguration.java} | 72 +++++-------------- .../org/exist/config/SaxonConfigTest.java | 6 +- 4 files changed, 28 insertions(+), 73 deletions(-) rename exist-core/src/main/java/org/exist/util/{SaxonConfigurationHolder.java => SaxonConfiguration.java} (73%) diff --git a/exist-core/src/main/java/org/exist/storage/BrokerPool.java b/exist-core/src/main/java/org/exist/storage/BrokerPool.java index af10028e1ac..40de0eb9edf 100644 --- a/exist-core/src/main/java/org/exist/storage/BrokerPool.java +++ b/exist-core/src/main/java/org/exist/storage/BrokerPool.java @@ -23,6 +23,7 @@ import com.evolvedbinary.j8fu.fsm.AtomicFSM; import com.evolvedbinary.j8fu.fsm.FSM; +import com.evolvedbinary.j8fu.lazy.AtomicLazyVal; import net.jcip.annotations.ThreadSafe; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -49,8 +50,8 @@ import org.exist.scheduler.Scheduler; import org.exist.scheduler.impl.QuartzSchedulerImpl; import org.exist.scheduler.impl.SystemTaskJobImpl; -import org.exist.security.*; import org.exist.security.SecurityManager; +import org.exist.security.*; import org.exist.security.internal.SecurityManagerImpl; import org.exist.storage.blob.BlobStore; import org.exist.storage.blob.BlobStoreImplService; @@ -115,6 +116,7 @@ public class BrokerPool extends BrokerPools implements BrokerPoolConstants, Data private final XQuery xqueryService = new XQuery(); + private AtomicLazyVal saxonConfiguration = new AtomicLazyVal<>(() -> SaxonConfiguration.loadConfiguration(this)); //TODO : make it non-static since every database instance may have its own policy. //TODO : make a default value that could be overwritten by the configuration @@ -373,13 +375,6 @@ public String getStatus() { private StartupTriggersManager startupTriggersManager; - /** - * Configuration for Saxon. - * - * One instance per-database, lazily initialised. - */ - private SaxonConfigurationHolder saxonConfigurationHolder; - /** * Creates and configures the database instance. * @@ -419,8 +414,6 @@ public String getStatus() { this.pageSize = conf.getProperty(PROPERTY_PAGE_SIZE, DEFAULT_PAGE_SIZE); - this.saxonConfigurationHolder = SaxonConfigurationHolder.getHolderForBroker(this); - //Configuration is valid, save it this.conf = conf; @@ -1927,11 +1920,11 @@ public void registerCollectionTrigger(final Class c } public net.sf.saxon.Configuration getSaxonConfiguration() { - return saxonConfigurationHolder.getConfiguration(); + return saxonConfiguration.get().getConfiguration(); } public net.sf.saxon.s9api.Processor getSaxonProcessor() { - return saxonConfigurationHolder.getProcessor(); + return saxonConfiguration.get().getProcessor(); } /** diff --git a/exist-core/src/main/java/org/exist/util/Configuration.java b/exist-core/src/main/java/org/exist/util/Configuration.java index 7df8a9e2e70..7c6a7c8a3db 100644 --- a/exist-core/src/main/java/org/exist/util/Configuration.java +++ b/exist-core/src/main/java/org/exist/util/Configuration.java @@ -261,7 +261,7 @@ public Configuration(String configFilename, Optional existHomeDirname) thr } //saxon settings (most importantly license file for PE or EE features) - final NodeList saxon = doc.getElementsByTagName(SaxonConfigurationHolder.SAXON_CONFIGURATION_ELEMENT_NAME); + final NodeList saxon = doc.getElementsByTagName(SaxonConfiguration.SAXON_CONFIGURATION_ELEMENT_NAME); if( saxon.getLength() > 0 ) { configureSaxon((Element)saxon.item(0)); } @@ -547,9 +547,9 @@ private void configureXUpdate( Element xupdate ) throws NumberFormatException private void configureSaxon( Element saxon ) { final String configurationFile = getConfigAttributeValue( saxon, - SaxonConfigurationHolder.SAXON_CONFIGURATION_FILE_ATTRIBUTE); + SaxonConfiguration.SAXON_CONFIGURATION_FILE_ATTRIBUTE); if (configurationFile != null) { - config.put(SaxonConfigurationHolder.SAXON_CONFIGURATION_FILE_PROPERTY, configurationFile); + config.put(SaxonConfiguration.SAXON_CONFIGURATION_FILE_PROPERTY, configurationFile); } } diff --git a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java b/exist-core/src/main/java/org/exist/util/SaxonConfiguration.java similarity index 73% rename from exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java rename to exist-core/src/main/java/org/exist/util/SaxonConfiguration.java index 13394777b7a..2528766c6a3 100644 --- a/exist-core/src/main/java/org/exist/util/SaxonConfigurationHolder.java +++ b/exist-core/src/main/java/org/exist/util/SaxonConfiguration.java @@ -21,7 +21,7 @@ */ package org.exist.util; -import com.evolvedbinary.j8fu.lazy.AtomicLazyVal; +import net.jcip.annotations.ThreadSafe; import net.sf.saxon.s9api.Processor; import net.sf.saxon.trans.XPathException; import org.exist.storage.BrokerPool; @@ -31,11 +31,10 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; -public final class SaxonConfigurationHolder { +@ThreadSafe +public final class SaxonConfiguration { public static final String SAXON_CONFIGURATION_ELEMENT_NAME = "saxon"; public static final String SAXON_CONFIGURATION_FILE_ATTRIBUTE = "configuration-file"; @@ -43,78 +42,43 @@ public final class SaxonConfigurationHolder { private static final String SAXON_DEFAULT_SAXON_CONFIG_FILE = "saxon-config.xml"; /** - * Maintain a separate, singleton Saxon configuration for each broker pool + * Holds the Saxon configuration specific to a single broker pool */ - private static final Map perBroker = new HashMap<>(); + private final net.sf.saxon.Configuration configuration; + private final net.sf.saxon.s9api.Processor processor; - private final BrokerPool brokerPool; - /** - * Load the Saxon configuration for this broker pool on demand - */ - private final AtomicLazyVal saxonConfiguration = new AtomicLazyVal<>(this::loadConfiguration); - - /** - * Create a Saxon processor from the Saxon configuration on demand - */ - private final AtomicLazyVal saxonProcessor = new AtomicLazyVal<>(this::createProcessor); - - /** - * Holds the Saxon configuration related to a broker pool - * Configuration elements are loaded on demand, - * initially this just holds a reference to the owning {@link BrokerPool} - * - * @param brokerPool which owns this Saxon configuration - */ - private SaxonConfigurationHolder(final BrokerPool brokerPool) { - this.brokerPool = brokerPool; + private SaxonConfiguration(final net.sf.saxon.Configuration configuration) { + this.configuration = configuration; + this.processor = new Processor(configuration); } /** - * Factory for the singleton Saxon configuration wrapper per broker pool - * @param brokerPool for which to fetch (and if necessary create) a Saxon configuration - * - * @return the associated Saxon configuration wrapper - */ - public static synchronized SaxonConfigurationHolder getHolderForBroker(final BrokerPool brokerPool) { - return perBroker.computeIfAbsent(brokerPool, SaxonConfigurationHolder::new); - } - - /** - * Get (lazy loading on first access) the Saxon API's {@link net.sf.saxon.Configuration} object + * Get the Saxon API's {@link net.sf.saxon.Configuration} object * * @return Saxon internal configuration object */ public net.sf.saxon.Configuration getConfiguration() { - return saxonConfiguration.get(); + return configuration; } /** - * Get (lazy loading on first access) the Saxon API's {@link Processor} through which Saxon operations + * Get the Saxon API's {@link Processor} through which Saxon operations * such as transformation can be effected * * @return the Saxon {@link Processor} associated with the configuration. */ public Processor getProcessor() { - return saxonProcessor.get(); - } - - /** - * Create the Saxon {@link Processor} from the {@link net.sf.saxon.Configuration} when it is first needed - * - * @return a freshly created Saxon processor - */ - private Processor createProcessor() { - return new Processor(saxonConfiguration.get()); + return processor; } /** * Load the Saxon {@link net.sf.saxon.Configuration} from a configuration file when it is first needed; - * if we cannot find a configuration file (and license) to give to Saxon, it may still be able to find - * something. + * if we cannot find a configuration file (and license) to give to Saxon, it (Saxon) may still be able to find + * something by searching in more "well-known to Saxon" locations. * * @return a freshly loaded Saxon configuration */ - private net.sf.saxon.Configuration loadConfiguration() { + public static SaxonConfiguration loadConfiguration(final BrokerPool brokerPool) { final var existConfiguration = brokerPool.getConfiguration(); final var saxonConfigFile = getSaxonConfigFile(existConfiguration); @@ -132,9 +96,9 @@ private net.sf.saxon.Configuration loadConfiguration() { "No default eXist Saxon configuration file " + SAXON_DEFAULT_SAXON_CONFIG_FILE); } - saxonConfiguration.ifPresent(SaxonConfigurationHolder::reportLicensedFeatures); + saxonConfiguration.ifPresent(SaxonConfiguration::reportLicensedFeatures); - return saxonConfiguration.get(); + return new SaxonConfiguration(saxonConfiguration.get()); } static private Optional readSaxonConfigurationFile(final File saxonConfigFile) { diff --git a/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java b/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java index f7ad8eb1e31..db6996ee5c4 100644 --- a/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java +++ b/exist-core/src/test/java/org/exist/config/SaxonConfigTest.java @@ -23,7 +23,6 @@ package org.exist.config; import org.exist.test.ExistEmbeddedServer; -import org.exist.util.SaxonConfigurationHolder; import org.junit.ClassRule; import org.junit.Test; @@ -41,12 +40,11 @@ public void configFromBroker() { final var existConfiguration = brokerPool.getConfiguration(); assertThat(existConfiguration.getProperty("saxon.configuration")).isEqualTo("saxon-config.xml"); - final var saxonConfigurationHolder = SaxonConfigurationHolder.getHolderForBroker(brokerPool); - final var saxonConfiguration = saxonConfigurationHolder.getConfiguration(); + final var saxonConfiguration = brokerPool.getSaxonConfiguration(); // There is no way to install EE at the test/build phase. // Sanity check is to confirm this does indeed return "HE" (Home Edition). - final var saxonProcessor = saxonConfigurationHolder.getProcessor(); + final var saxonProcessor = brokerPool.getSaxonProcessor(); assertThat(saxonProcessor.getSaxonEdition()).isEqualTo("HE"); final var saxonConfiguration2 = brokerPool.getSaxonConfiguration(); From 4170f09ae5a28b8d6122d48a34713dfaa41f59de Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Mon, 24 Apr 2023 15:52:56 +0100 Subject: [PATCH 07/14] [ignore] add some comment text, fix eXist-DB case --- exist-distribution/src/main/config/conf.xml | 4 ++++ exist-distribution/src/main/config/saxon-config.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/exist-distribution/src/main/config/conf.xml b/exist-distribution/src/main/config/conf.xml index f646a11785f..e7058e53e3f 100644 --- a/exist-distribution/src/main/config/conf.xml +++ b/exist-distribution/src/main/config/conf.xml @@ -1106,6 +1106,10 @@ diff --git a/exist-distribution/src/main/config/saxon-config.xml b/exist-distribution/src/main/config/saxon-config.xml index 332f95f8c20..6afee1aad7c 100644 --- a/exist-distribution/src/main/config/saxon-config.xml +++ b/exist-distribution/src/main/config/saxon-config.xml @@ -1,5 +1,5 @@ diff --git a/exist-distribution/src/main/config/saxon-config.xml b/exist-distribution/src/main/config/saxon-config.xml index 6afee1aad7c..5305fe18d4b 100644 --- a/exist-distribution/src/main/config/saxon-config.xml +++ b/exist-distribution/src/main/config/saxon-config.xml @@ -1,6 +1,7 @@