Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7e4e72e
adds SPARQL plugin
lueck Apr 21, 2026
99904d8
adds basic tests for SPARQL plugin
lueck Apr 24, 2026
57eff25
adds Serializer bean for choosing working output format
lueck Apr 24, 2026
90e8bfd
implements method for getting output media type
lueck Apr 24, 2026
4219d78
Merge branch 'refs/heads/main' into sparql
lueck Apr 24, 2026
8f5c5b7
adds content negotiation to SPARQL plugin
lueck Apr 24, 2026
2fe9773
renamed to SparqlConstruct
lueck Apr 24, 2026
39a444d
about the plugin
lueck Apr 24, 2026
e64276b
adds JSON-LD framing with Titanium
lueck Apr 24, 2026
f9d4de9
makes JSON-LD context for framing configurable
lueck Apr 26, 2026
264629e
encapsulates preparation of the JSON-LD framing context
lueck Apr 26, 2026
49823aa
fixes resource management when getting context
lueck Apr 26, 2026
b0dd4bc
make SPARQL plugin available in DTS
lueck Apr 26, 2026
c749554
return after branching
lueck Apr 26, 2026
be1f810
introduces size limit for JSON-LD context
lueck Apr 26, 2026
144adc3
about the plugin's application properties
lueck Apr 26, 2026
f03da5d
adds SPARQL plugin
lueck Apr 21, 2026
3246704
adds basic tests for SPARQL plugin
lueck Apr 24, 2026
9875229
adds Serializer bean for choosing working output format
lueck Apr 24, 2026
84eacb4
implements method for getting output media type
lueck Apr 24, 2026
06b8d5b
adds content negotiation to SPARQL plugin
lueck Apr 24, 2026
aac3475
renamed to SparqlConstruct
lueck Apr 24, 2026
7629901
about the plugin
lueck Apr 24, 2026
6eea4fb
adds JSON-LD framing with Titanium
lueck Apr 24, 2026
d8bd7fb
makes JSON-LD context for framing configurable
lueck Apr 26, 2026
5911cd2
encapsulates preparation of the JSON-LD framing context
lueck Apr 26, 2026
9c624f5
fixes resource management when getting context
lueck Apr 26, 2026
dfa86dd
make SPARQL plugin available in DTS
lueck Apr 26, 2026
a322fbb
return after branching
lueck Apr 26, 2026
488b41e
introduces size limit for JSON-LD context
lueck Apr 26, 2026
7449031
about the plugin's application properties
lueck Apr 26, 2026
5719ff6
Merge branch 'sparql' of github.com:SCDH/seed-xc into sparql
lueck Apr 27, 2026
29e195e
assert no resource leaks from input stream
lueck Apr 27, 2026
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
12 changes: 12 additions & 0 deletions api/src/main/resources/openapi/seed-xc-openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ components:
type:
type: string
description: The local name of the parameter type. Write 'integer' instead of 'xs:integer'!
context:
description: Options of the JSON-LD context
type: object
required:
- location
properties:
location:
type: string
format: uri
description: The URI of the initial active context
transformationInfo:
description: Information about the transformation resource and its runtime parameters
type: object
Expand Down Expand Up @@ -490,6 +500,8 @@ components:
$ref: '#/components/schemas/parser'
serializer:
$ref: '#/components/schemas/serializer'
context:
$ref: '#/components/schemas/context'
required:
- class
- description
Expand Down
10 changes: 10 additions & 0 deletions dts/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@
<artifactId>seed-xc-transformations</artifactId>
<version>${revision}${changelist}</version>
</dependency>
<!--
Add plugins to the classpath
important: scope should be runtime or test.
-->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seed-xc-saxon</artifactId>
<version>${revision}${changelist}</version>
<scope>${plugins.scope}</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seed-xc-sparql</artifactId>
<version>${revision}${changelist}</version>
<scope>${plugins.scope}</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seed-resource-providers</artifactId>
Expand Down
1 change: 1 addition & 0 deletions plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<modules>
<module>resource-providers</module>
<module>saxon</module>
<module>seed-xc-sparql</module>
</modules>

<build>
Expand Down
35 changes: 35 additions & 0 deletions plugins/seed-xc-sparql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPARQL Plugins

This module provides transformation plugins. It is based on
[Apache Jena](https://jena.apache.org/) and uses
[Titanium JSON-LD](https://github.com/filip26/titanium-json-ld)
for JSON-LD framing.

For JSON-LD contexts used in framing, this plugin allows outbound
URL connections passing by the `ResourceProvider`.

## Transformation Types

- `sparql-construct`

## Content Negotiation

- If `TransformationInfo.mediaType` is present, then and only then it determines the returned format.
- If there's no content type on the transformation level and content negotiation by the request's `Accept` is used.
- In case no content type is defined on the transformation level nor one is requested, the content tpye of input resource will be used
- As a fallback, a default content type is used.

## Transformation Parameters

Request parameters are passed to the query with a mechanism by
[Apache Jena](https://jena.apache.org/documentation/query/parameterized-sparql-strings.html).
It replaces all occurrences of a SPARQL variable `?X` with a literal.

Parameter types should be declared with the parameter descripter as `xs:...` types.
`xs:string` is used as default.

## Application Properties

- `url-connect-timeout`: time limit for establishing a connection to a remote JSON-LD context URL for framing, defaults to 10s
- `url-read-timeout`: time limit for fetching a remote JSON-LD context for framing, defaults to 10s
- `context-max-size`: size limit of JSON-LD context for framing, defaults to 1MB
109 changes: 109 additions & 0 deletions plugins/seed-xc-sparql/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.ulbms.scdh.seed.xc</groupId>
<artifactId>seed-xc-plugins</artifactId>
<version>${revision}${changelist}</version>
</parent>

<artifactId>seed-xc-sparql</artifactId>
<name>SEED XC SPARQL</name>
<description>A plugin for transformations build from SPARQL queries</description>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jena.version>6.0.0</jena.version>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seed-xc-api</artifactId>
<version>${revision}${changelist}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seed-resource-providers</artifactId>
<version>${revision}${changelist}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>jena-arq</artifactId>
<version>${jena.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>

<!-- index the beans in this module, see
https://quarkus.io/guides/cdi-reference#how-to-generate-a-jandex-index
-->
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>${smallrye-jandex-plugin.version}</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>

<!-- flatten the pom -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration/>
<executions>
<!-- enable flattening -->
<execution>
<id>flatten</id>
<goals>
<goal>flatten</goal>
</goals>
<phase>process-resources</phase>
</execution>
<!-- ensure proper cleanup -->
<execution>
<id>flatten.clean</id>
<goals>
<goal>clean</goal>
</goals>
<phase>clean</phase>
</execution>
</executions>
</plugin>

</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package de.ulbms.scdh.seed.xc.jena;

import de.ulbms.scdh.seed.xc.api.TransformationPreparationException;
import jakarta.enterprise.context.ApplicationScoped;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.apache.jena.query.ParameterizedSparqlString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link ParameterConverter} class casts strings to Java objects based on a xs-type.
*/
@ApplicationScoped
public class ParameterConverter {

private static final Logger LOG = LoggerFactory.getLogger(ParameterConverter.class);

/**
* Sets the variable with <code>name</code> in the supplied query.
*
* @param name - Name of the SPARQL variable
* @param value - Value to be set
* @param type - type information
* @param query - the parametrized query
* @throws TransformationPreparationException - on a cast failure
*/
public void setQueryParameter(String name, String value, String type, ParameterizedSparqlString query)
throws TransformationPreparationException {
switch (type) {
case "xs:anyURI" -> query.setIri(name, value);
case "xs:string" -> query.setLiteral(name, value);
case "xs:integer" -> {
try {
query.setLiteral(name, Integer.parseInt(value));
} catch (NumberFormatException e) {
LOG.error("failed to cast '{}' value of parameter {} to integer", value, name);
throw new TransformationPreparationException("failed to set parameter " + name, e);
}
}
case "xs:long" -> {
try {
query.setLiteral(name, Long.parseLong(value));
} catch (NumberFormatException e) {
LOG.error("failed to cast '{}' value of parameter {} to long", value, name);
throw new TransformationPreparationException("failed to set parameter " + name, e);
}
}
case "xs:float" -> {
try {
query.setLiteral(name, Float.parseFloat(value));
} catch (NumberFormatException e) {
LOG.error("failed to cast '{}' value of parameter {} to float", value, name);
throw new TransformationPreparationException("failed to set parameter " + name, e);
}
}
case "xs:double" -> {
try {
query.setLiteral(name, Double.parseDouble(value));
} catch (NumberFormatException e) {
LOG.error("failed to cast '{}' value of parameter {} to double", value, name);
throw new TransformationPreparationException("failed to set parameter " + name, e);
}
}
case "xs:date" -> {
try {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat();
calendar.setTime(sdf.parse(value));
query.setLiteral(name, calendar);
} catch (ParseException e) {
LOG.error("failed to cast '{}' value of parameter {} to calendar", value, name);
throw new TransformationPreparationException("failed to set parameter " + name, e);
}
}
default -> {
// defaults to string again
LOG.error("no valid type information for parameter {}: {}. Using string", name, type);
query.setLiteral(name, value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package de.ulbms.scdh.seed.xc.jena;

import de.ulbms.scdh.seed.xc.api.TransformationPreparationException;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFFormat;
import org.apache.jena.riot.RDFLanguages;

/**
* The {@link Serializer} adds sufficient information for getting an RDF output format that works.
* This includes choosing an encoding variant.
*/
@ApplicationScoped
public class Serializer {

public static final Lang DEFAULT = Lang.N3;

/**
* This implementation prefers the content type declared for the transformation (first argument)
* over the accept header of the request. If none is given, the content type is guessed from the
* processed file extension. <code>DEFAULT</code> is returned as fallback.
*
* @param transformationContentType - the content type declared for the transformation
* @param systemId - the name of the request file
* @param request - HTTP request with accept headers
* @return the RDF content type
*/
public static RDFFormat getFormat(String transformationContentType, String systemId, HttpServerRequest request)
throws TransformationPreparationException {
try {
if (transformationContentType != null) {
Lang lang = RDFLanguages.contentTypeToLang(transformationContentType);
return getFormatVariant(lang, "utf-8");
} else if (request != null & request.getHeader(HttpHeaders.ACCEPT) != null) {
Lang lang = RDFLanguages.contentTypeToLang(request.getHeader(HttpHeaders.ACCEPT));
return getFormatVariant(lang, request.getHeader(HttpHeaders.ACCEPT_CHARSET));
} else {
return new RDFFormat(RDFLanguages.filenameToLang(systemId, DEFAULT));
}
} catch (Exception e) {
throw new TransformationPreparationException(
"unknown RDF format: " + transformationContentType + " " + request.getHeader(HttpHeaders.ACCEPT));
}
}

/**
* This adds missing information to get a format variant.
*
* @param lang - the basic format as {@link Lang}
* @param charset - the charset requested
* @return - the fully specified format for which a formatter exists
*/
protected static RDFFormat getFormatVariant(Lang lang, String charset) {
if (lang.equals(Lang.NTRIPLES)) {
return new RDFFormat(lang, RDFFormat.UTF8);
} else if (lang.equals(Lang.TTL)) {
return new RDFFormat(lang, RDFFormat.PRETTY);
} else if (lang.equals(Lang.RDFXML)) {
return new RDFFormat(lang, RDFFormat.PLAIN);
} else if (lang.equals(Lang.JSONLD)) {
return RDFFormat.JSONLD11_PRETTY;
} else {
return new RDFFormat(lang);
}
}
}
Loading