Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Allow Saxon licensed editions (EE and PE) #4854

Merged
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
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 @@

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 {

Check warning on line 131 in exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

exist-core/src/main/java/org/exist/xquery/functions/fn/transform/Options.java#L131

The constructor 'Options(XQueryContext, FnTransform, Convert.ToSaxon, MapType)' has an NPath complexity of 360, current threshold is 200
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 @@
" 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 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 @@

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

@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 @@
return Objects.hash(major, minor);
}

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