Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import javax.xml.transform.stream.StreamSource;

import com.lyncode.xoai.dataprovider.services.api.ResourceResolver;
import net.sf.saxon.jaxp.SaxonTransformerFactory;
import net.sf.saxon.s9api.ExtensionFunction;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
Expand All @@ -46,6 +45,9 @@ public class DSpaceResourceResolver implements ResourceResolver {
private static final TransformerFactory transformerFactory = TransformerFactory
.newInstance("net.sf.saxon.TransformerFactoryImpl", null);
static {
// Initialize the shared processor first
SharedSaxonProcessor.initialize(transformerFactory);

/*
* Any additional extension functions that might be used in XST transformations
* should be added to this list. Look at those already added for inspiration.
Expand All @@ -58,10 +60,9 @@ public class DSpaceResourceResolver implements ResourceResolver {
new BibtexifyFn(), new FormatFn(), new GetAvailableFn()
);

SaxonTransformerFactory saxonTransformerFactory = (SaxonTransformerFactory) transformerFactory;
for (ExtensionFunction en :
extensionFunctionList) {
saxonTransformerFactory.getProcessor().registerExtensionFunction(en);
// Use the shared processor to ensure configuration compatibility
for (ExtensionFunction en : extensionFunctionList) {
SharedSaxonProcessor.getProcessor().registerExtensionFunction(en);
}
}

Expand All @@ -87,6 +88,9 @@ public Templates getTemplates(String path) throws IOException, TransformerConfig
// XSLT-files (like <xsl:import href="utils.xsl"/>)
String systemId = basePath + "/" + path;
mySrc.setSystemId(systemId);
return transformerFactory.newTemplates(mySrc);

// Use the shared Saxon transformer factory to ensure
// compatibility with registered extension functions
return SharedSaxonProcessor.getTransformerFactory().newTemplates(mySrc);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.xoai.services.impl.resources;

import javax.xml.transform.TransformerFactory;

import net.sf.saxon.jaxp.SaxonTransformerFactory;
import net.sf.saxon.s9api.Processor;

/**
* Utility class to provide a shared Saxon Processor and TransformerFactory instance.
*
* This class maintains a singleton Processor and SaxonTransformerFactory to avoid
* configuration incompatibility issues between different Saxon processors.
*
* Initialization must be done once by calling {@link #initialize(TransformerFactory)}.
* The class is thread-safe: the shared instances are lazily initialized with synchronization.
*
* @author Michaela Stefancova (dspace at dataquest.sk)
*/
public final class SharedSaxonProcessor {
private SharedSaxonProcessor() {
// utility class
}

private static volatile SaxonTransformerFactory saxonTransformerFactory;
private static volatile Processor sharedProcessor;

/**
* Initialize the shared processor with the given TransformerFactory.
* Must be called once before accessing any shared processor or builder.
*
* Synchronized to prevent race conditions if multiple threads attempt initialization.
*
* @param transformerFactory the Saxon TransformerFactory to use
* @throws IllegalArgumentException if transformerFactory is null or not a SaxonTransformerFactory
*/
public static synchronized void initialize(TransformerFactory transformerFactory) {
if (saxonTransformerFactory == null) {
if (transformerFactory == null) {
throw new IllegalArgumentException("TransformerFactory cannot be null");
}
if (!(transformerFactory instanceof SaxonTransformerFactory)) {
throw new IllegalArgumentException("TransformerFactory must be an instance of SaxonTransformerFactory");
}
saxonTransformerFactory = (SaxonTransformerFactory) transformerFactory;
sharedProcessor = saxonTransformerFactory.getProcessor();
}
}

/**
* Get the shared Saxon Processor instance.
*
* @return the shared Processor
* @throws IllegalStateException if initialize() has not been called yet
*/
public static Processor getProcessor() {
if (sharedProcessor == null) {
throw new IllegalStateException("SharedSaxonProcessor has not been initialized. Call initialize() first.");
}
return sharedProcessor;
}

/**
* Get the shared Saxon TransformerFactory instance.
*
* @return the shared SaxonTransformerFactory
* @throws IllegalStateException if initialize() has not been called yet
*/
public static SaxonTransformerFactory getTransformerFactory() {
if (saxonTransformerFactory == null) {
throw new IllegalStateException("SharedSaxonProcessor has not been initialized. Call initialize() first.");
}
return saxonTransformerFactory;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import static org.dspace.xoai.services.impl.resources.functions.StringXSLFunction.BASE;

import java.util.Arrays;
import java.util.Objects;

import net.sf.saxon.s9api.ExtensionFunction;
Expand All @@ -20,14 +21,16 @@
import net.sf.saxon.s9api.SequenceType;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmValue;
import org.bouncycastle.util.Arrays;
import org.apache.logging.log4j.Logger;

/**
* Serves as proxy for call from XSL engine.
* @author Marian Berger (marian.berger at dataquest.sk)
*/
public abstract class ListXslFunction implements ExtensionFunction {

private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ListXslFunction.class);

protected abstract String getFnName();

protected abstract String getStringResponse(String param);
Expand All @@ -50,15 +53,39 @@ final public SequenceType[] getArgumentTypes() {
}

@Override
final public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException {
if (Objects.isNull(xdmValues) || Arrays.isNullOrContainsNull(xdmValues)) {
public final XdmValue call(XdmValue[] xdmValues) throws SaxonApiException {
if (xdmValues == null || xdmValues.length == 0) {
log.debug("Null or empty parameters passed to {}, returning empty string", getFnName());
return new XdmAtomicValue("");
}
String response = "";
for (XdmValue item :
xdmValues) {
response += getStringResponse(item.itemAt(0).getStringValue());
if (Arrays.stream(xdmValues).anyMatch(Objects::isNull)) {
log.debug("Null or empty parameters passed to {}, returning empty string", getFnName());
return new XdmAtomicValue("");
}
return new XdmAtomicValue(response);

StringBuilder response = new StringBuilder();

for (XdmValue arg : xdmValues) {
if (arg == null || arg.size() == 0) {
continue;
}

for (int i = 0; i < arg.size(); i++) {
try {
String param = arg.itemAt(i).getStringValue();
String result = getStringResponse(param);
if (result != null) {
response.append(result);
}
} catch (Exception e) {
log.warn("Error processing parameter in function {}: {}", getFnName(), e.getMessage());
}
}
}

String finalResponse = response.toString();
log.debug("Function {} processed {} parameters and returned response of length {}",
getFnName(), xdmValues.length, finalResponse.length());
return new XdmAtomicValue(finalResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@

import static org.dspace.xoai.services.impl.resources.functions.StringXSLFunction.BASE;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.stream.Collectors;

import net.sf.saxon.s9api.ExtensionFunction;
import net.sf.saxon.s9api.ItemType;
Expand All @@ -23,13 +21,10 @@
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.SequenceType;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmEmptySequence;
import net.sf.saxon.s9api.XdmValue;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.util.Arrays;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


/**
* Serves as proxy for call from XSL engine.
Expand All @@ -40,59 +35,51 @@
public abstract class NodeListXslFunction implements ExtensionFunction {

private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(NodeListXslFunction.class);
protected abstract String getFnName();

protected abstract String getFnName();
protected abstract List<String> getList(String param);

@Override
final public QName getName() {
public final QName getName() {
return new QName(BASE, getFnName());
}

@Override
final public SequenceType getResultType() {
public final SequenceType getResultType() {
return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_MORE);
}

@Override
final public SequenceType[] getArgumentTypes() {
public final SequenceType[] getArgumentTypes() {
return new SequenceType[]{
SequenceType.makeSequenceType(
ItemType.STRING, OccurrenceIndicator.ZERO_OR_MORE)};
SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_MORE)
};
}

@Override
final public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException {
if (Objects.isNull(xdmValues) || Arrays.isNullOrContainsNull(xdmValues)) {
return new XdmAtomicValue("");
public final XdmValue call(XdmValue[] xdmValues) throws SaxonApiException {
if (Objects.isNull(xdmValues) || Arrays.isNullOrContainsNull(xdmValues) || xdmValues.length == 0) {
return XdmEmptySequence.getInstance();
}

String val;
try {
val = xdmValues[0].itemAt(0).getStringValue();
} catch (Exception e) {
// e.g. when no parameter is passed and xdmValues[0] ends with index error
log.warn("Empty value in call of function of NodeListXslFunction type");
val = "";
log.warn("Empty value in call of function {}, returning empty sequence", getFnName());
return XdmEmptySequence.getInstance();
}

List<String> list = getList(val);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
javax.xml.parsers.DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
Document newDoc = db.newDocument();
Element rootElement = newDoc.createElement("root");
newDoc.appendChild(rootElement);

List<XdmItem> items = new LinkedList<>();
for (String item : list) {
items.add(new XdmAtomicValue(item));
}
return new XdmValue(items);

} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
if (list == null || list.isEmpty()) {
return XdmEmptySequence.getInstance();
}

// Convert list of strings to XdmValue using streams
return new XdmValue(
list.stream()
.map(XdmAtomicValue::new)
.collect(Collectors.toList())
);
}
}
}
Loading
Loading