Skip to content

Commit

Permalink
Merge pull request #4854 from evolvedbinary/feature/eb-4813-allow-sax…
Browse files Browse the repository at this point in the history
…on-licensed-editions

[feature] Allow Saxon licensed editions (EE and PE)
  • Loading branch information
adamretter committed May 10, 2023
2 parents b10c41c + 8da4669 commit ba68294
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 37 deletions.
16 changes: 7 additions & 9 deletions exist-core/src/main/java/org/exist/storage/BrokerPool.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,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;
Expand Down Expand Up @@ -116,6 +116,7 @@ public class BrokerPool extends BrokerPools implements BrokerPoolConstants, Data

private final XQuery xqueryService = new XQuery();

private AtomicLazyVal<SaxonConfiguration> 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
Expand Down Expand Up @@ -374,13 +375,6 @@ public String getStatus() {

private StartupTriggersManager startupTriggersManager;

/**
* Configuration for Saxon.
*
* One instance per-database, lazily initialised.
*/
private AtomicLazyVal<net.sf.saxon.Configuration> saxonConfig = new AtomicLazyVal<>(net.sf.saxon.Configuration::newConfiguration);

/**
* Creates and configures the database instance.
*
Expand Down Expand Up @@ -1926,7 +1920,11 @@ public void registerCollectionTrigger(final Class<? extends CollectionTrigger> c
}

public net.sf.saxon.Configuration getSaxonConfiguration() {
return saxonConfig.get();
return saxonConfiguration.get().getConfiguration();
}

public net.sf.saxon.s9api.Processor getSaxonProcessor() {
return saxonConfiguration.get().getProcessor();
}

/**
Expand Down
14 changes: 14 additions & 0 deletions exist-core/src/main/java/org/exist/util/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ public Configuration(String configFilename, Optional<Path> existHomeDirname) thr
configureTransformer((Element)transformers.item(0));
}

//saxon settings (most importantly license file for PE or EE features)
final NodeList saxon = doc.getElementsByTagName(SaxonConfiguration.SAXON_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) {
Expand Down Expand Up @@ -538,6 +544,14 @@ private void configureXUpdate( Element xupdate ) throws NumberFormatException
}
}

private void configureSaxon( Element saxon )
{
final String configurationFile = getConfigAttributeValue( saxon,
SaxonConfiguration.SAXON_CONFIGURATION_FILE_ATTRIBUTE);
if (configurationFile != null) {
config.put(SaxonConfiguration.SAXON_CONFIGURATION_FILE_PROPERTY, configurationFile);
}
}

private void configureTransformer( Element transformer )
{
Expand Down
186 changes: 186 additions & 0 deletions exist-core/src/main/java/org/exist/util/SaxonConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* 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 net.jcip.annotations.ThreadSafe;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.trans.XPathException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.storage.BrokerPool;

import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

@ThreadSafe
public final class SaxonConfiguration {

private final static Logger LOG = LogManager.getLogger(SaxonConfiguration.class);

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

/**
* Holds the Saxon configuration specific to a single broker pool
*/
private final net.sf.saxon.Configuration configuration;
private final Processor processor;

private SaxonConfiguration(final net.sf.saxon.Configuration configuration) {
this.configuration = configuration;
this.processor = new Processor(configuration);
}

/**
* Get the Saxon API's {@link net.sf.saxon.Configuration} object.
*
* @return Saxon internal configuration object
*/
public net.sf.saxon.Configuration getConfiguration() {
return configuration;
}

/**
* 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 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 (Saxon) may still be able to find
* something by searching in more "well-known to Saxon" locations.
*
* @return a freshly loaded Saxon configuration
*/
public static SaxonConfiguration loadConfiguration(final BrokerPool brokerPool) {

final var existConfiguration = brokerPool.getConfiguration();
final var saxonConfigFile = getSaxonConfigFile(existConfiguration);
Optional<net.sf.saxon.Configuration> saxonConfiguration = Optional.empty();
if (saxonConfigFile.isPresent()) {
saxonConfiguration = readSaxonConfigurationFile(saxonConfigFile.get());
}

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 " + SAXON_CONFIGURATION_FILE_PROPERTY + "\n" +
"No default eXist Saxon configuration file " + SAXON_DEFAULT_SAXON_CONFIG_FILE);
}

saxonConfiguration.ifPresent(SaxonConfiguration::reportLicensedFeatures);

return new SaxonConfiguration(saxonConfiguration.get());
}

static private Optional<net.sf.saxon.Configuration> readSaxonConfigurationFile(final Path saxonConfigFile) {
try {
return Optional.of(net.sf.saxon.Configuration.readConfiguration(
new StreamSource(Files.newInputStream(saxonConfigFile))));
} catch (final XPathException | IOException 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 {
throw runtimeException;
}
}
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.isEmpty()) {
LOG.info("Saxon - no licensed features reported.");
} else {
LOG.info("Saxon - licensed features are" + sb + ".");
}
}

/**
* 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 Path resolveConfigurationFile(final Configuration existConfiguration, final String filename) {
final var configurationFile = Paths.get(filename);
if (configurationFile.isAbsolute()) {
return configurationFile;
}

final var configPath = existConfiguration.getConfigFilePath();
return configPath.map(p -> p.getParent().resolve(configurationFile)).orElse(configurationFile);
}

private static Optional<Path> getSaxonConfigFile(final Configuration existConfiguration) {
if (existConfiguration.getProperty(SAXON_CONFIGURATION_FILE_PROPERTY) instanceof String saxonConfigurationFile) {
final var configurationFile = resolveConfigurationFile(existConfiguration, saxonConfigurationFile);
if (Files.isReadable(configurationFile)) {
return Optional.of(configurationFile);
} else {
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, SAXON_DEFAULT_SAXON_CONFIG_FILE);
if (Files.isReadable(configurationFile)) {
return Optional.of(configurationFile);
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ 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 final Transform transform;

public FnTransform(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
this.transform = new Transform(context, this);
}

@Override
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
return transform.eval(args, contextSequence);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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 +
Expand Down Expand Up @@ -519,7 +522,7 @@ private Source resolveStylesheetLocation(final String stylesheetLocation) throws
*/
private Source resolvePossibleStylesheetLocation(final String location) throws XPathException {

Sequence document;
final Sequence document;
try {
document = DocUtils.getDocument(context, location);
} catch (final PermissionDeniedException e) {
Expand Down Expand Up @@ -651,13 +654,14 @@ public Reader getReader() {

static class SystemProperties {

private SystemProperties() {
super();
}
private final RetainedStaticContext retainedStaticContext;

private static final RetainedStaticContext retainedStaticContext = new RetainedStaticContext(SAXON_CONFIGURATION);
private SystemProperties(final XQueryContext context) {
final var saxonConfiguration = context.getBroker().getBrokerPool().getSaxonConfiguration();
this.retainedStaticContext = new RetainedStaticContext(saxonConfiguration);
}

static String get(org.exist.dom.QName qName) {
String get(final org.exist.dom.QName qName) {
return SystemProperty.getProperty(qName.getNamespaceURI(), qName.getLocalPart(), retainedStaticContext);
}
}
Expand All @@ -682,10 +686,14 @@ public static XSLTVersion fromDecimal(final BigDecimal decimal) throws Transform
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
XSLTVersion version = (XSLTVersion) o;
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final XSLTVersion version = (XSLTVersion) o;
return major == version.major && minor == version.minor;
}

Expand All @@ -694,7 +702,8 @@ public int hashCode() {
return Objects.hash(major, minor);
}

@Override public String toString() {
@Override
public String toString() {
return major + "." + minor;
}
}
Expand Down
Loading

0 comments on commit ba68294

Please sign in to comment.