From 65687ee9802db5207a8d318d5f34a99c973e44b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 5 Mar 2026 22:33:39 +0100 Subject: [PATCH 1/5] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 781efb2ed..2586a6b1a 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.atomgraph linkeddatahub - 5.3.0 + 5.3.1-SNAPSHOT ${packaging.type} AtomGraph LinkedDataHub @@ -46,7 +46,7 @@ https://github.com/AtomGraph/LinkedDataHub scm:git:git://github.com/AtomGraph/LinkedDataHub.git scm:git:git@github.com:AtomGraph/LinkedDataHub.git - linkeddatahub-5.3.0 + linkeddatahub-2.1.1 From a304c44e993498d7236a313b2b24338df7e51f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 18 Mar 2026 10:03:29 +0100 Subject: [PATCH 2/5] Namespace endpoint handles queries with relative URIs The relative URIs are resolved against the endpoint URL as the base URI HTTP tests included --- .../query/GET-ns-relative-uri.sh | 40 ++++++++++++++++++ .../query/POST-ns-relative-uri.sh | 41 +++++++++++++++++++ .../linkeddatahub/resource/Namespace.java | 5 +++ .../xsl/bootstrap/2.3.2/layout.xsl | 1 - 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100755 http-tests/sparql-protocol/query/GET-ns-relative-uri.sh create mode 100644 http-tests/sparql-protocol/query/POST-ns-relative-uri.sh diff --git a/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh b/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh new file mode 100755 index 000000000..c8fc4fb4b --- /dev/null +++ b/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +initialize_dataset "$END_USER_BASE_URL" "$TMP_END_USER_DATASET" "$END_USER_ENDPOINT_URL" +initialize_dataset "$ADMIN_BASE_URL" "$TMP_ADMIN_DATASET" "$ADMIN_ENDPOINT_URL" +purge_cache "$END_USER_VARNISH_SERVICE" +purge_cache "$ADMIN_VARNISH_SERVICE" +purge_cache "$FRONTEND_VARNISH_SERVICE" + +# create class in the namespace ontology + +namespace_doc="${END_USER_BASE_URL}ns" +namespace="${namespace_doc}#" +ontology_doc="${ADMIN_BASE_URL}ontologies/namespace/" +class="${namespace}NewClass" + +add-class.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + -b "$ADMIN_BASE_URL" \ + --uri "$class" \ + --label "New class" \ + "$ontology_doc" + +# clear ontology from memory + +clear-ontology.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + -b "$ADMIN_BASE_URL" \ + --ontology "$namespace" + +# query using relative URI - <#NewClass> should resolve to ${namespace_doc}#NewClass + +curl -k -s -G \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -H "Accept: application/sparql-results+xml" \ + "${namespace_doc}" \ + --data-urlencode "query=SELECT * { <#NewClass> ?p ?o }" \ +| grep 'New class' > /dev/null diff --git a/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh b/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh new file mode 100644 index 000000000..46e1d3907 --- /dev/null +++ b/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +initialize_dataset "$END_USER_BASE_URL" "$TMP_END_USER_DATASET" "$END_USER_ENDPOINT_URL" +initialize_dataset "$ADMIN_BASE_URL" "$TMP_ADMIN_DATASET" "$ADMIN_ENDPOINT_URL" +purge_cache "$END_USER_VARNISH_SERVICE" +purge_cache "$ADMIN_VARNISH_SERVICE" +purge_cache "$FRONTEND_VARNISH_SERVICE" + +# create class in the namespace ontology + +namespace_doc="${END_USER_BASE_URL}ns" +namespace="${namespace_doc}#" +ontology_doc="${ADMIN_BASE_URL}ontologies/namespace/" +class="${namespace}NewClass" + +add-class.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + -b "$ADMIN_BASE_URL" \ + --uri "$class" \ + --label "New class" \ + "$ontology_doc" + +# clear ontology from memory + +clear-ontology.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + -b "$ADMIN_BASE_URL" \ + --ontology "$namespace" + +# query using relative URI - <#NewClass> should resolve to ${namespace_doc}#NewClass + +curl -k -s -X POST \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -H "Accept: application/sparql-results+xml" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + "${namespace_doc}" \ + --data-urlencode "query=SELECT * { <#NewClass> ?p ?o }" \ +| grep 'New class' > /dev/null diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java b/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java index 8c234be2a..095219cf3 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java @@ -51,6 +51,7 @@ import org.apache.jena.ontology.Ontology; import org.apache.jena.query.DatasetFactory; import org.apache.jena.query.Query; +import org.apache.jena.query.QueryFactory; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.riot.system.Checker; @@ -148,6 +149,8 @@ public Response get(@QueryParam(QUERY) Query query, else throw new BadRequestException("SPARQL query string not provided"); } + // re-parse query to set explicit fallback base URI + query = QueryFactory.create(query.toString(), getUriInfo().getAbsolutePath().toString()); return super.get(query, defaultGraphUris, namedGraphUris); } @@ -160,6 +163,8 @@ public Response post(@FormParam(QUERY) String queryString, @FormParam(UPDATE) St { if (updateString != null) throw new WebApplicationException("SPARQL updates are not allowed on the endpoint", Status.METHOD_NOT_ALLOWED); + // re-parse query to set explicit fallback base URI + queryString = QueryFactory.create(queryString, getUriInfo().getAbsolutePath().toString()).toString(); return super.post(queryString, updateString, defaultGraphUris, namedGraphUris, usingGraphUris, usingNamedGraphUris); } diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/layout.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/layout.xsl index 9ed7ae6e6..4bdd0c247 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/layout.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/layout.xsl @@ -100,7 +100,6 @@ exclude-result-prefixes="#all"> - From e6e17195927527738d8e8e00ed9df05814c510f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 18 Mar 2026 10:09:02 +0100 Subject: [PATCH 3/5] Fixed comment --- http-tests/sparql-protocol/query/GET-ns-relative-uri.sh | 2 +- http-tests/sparql-protocol/query/POST-ns-relative-uri.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh b/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh index c8fc4fb4b..2ea264ba8 100755 --- a/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh +++ b/http-tests/sparql-protocol/query/GET-ns-relative-uri.sh @@ -30,7 +30,7 @@ clear-ontology.sh \ -b "$ADMIN_BASE_URL" \ --ontology "$namespace" -# query using relative URI - <#NewClass> should resolve to ${namespace_doc}#NewClass +# query using relative URI - <#NewClass> should resolve to ${namespace}NewClass curl -k -s -G \ -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ diff --git a/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh b/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh index 46e1d3907..a0654c887 100644 --- a/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh +++ b/http-tests/sparql-protocol/query/POST-ns-relative-uri.sh @@ -30,7 +30,7 @@ clear-ontology.sh \ -b "$ADMIN_BASE_URL" \ --ontology "$namespace" -# query using relative URI - <#NewClass> should resolve to ${namespace_doc}#NewClass +# query using relative URI - <#NewClass> should resolve to ${namespace}NewClass curl -k -s -X POST \ -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ From f9d6f70681b889e54a07326d23bfe37fe5f6840e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 19 Mar 2026 12:31:28 +0100 Subject: [PATCH 4/5] GRDDL filters loaded susing ServiceLoader --- .../atomgraph/linkeddatahub/Application.java | 26 ++++++++------ .../filter/JSONGRDDLFilterProvider.java | 31 ++++++++++++++++ .../grddl/YouTubeGRDDLFilterProvider.java | 36 +++++++++++++++++++ ...ahub.client.filter.JSONGRDDLFilterProvider | 1 + 4 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/atomgraph/linkeddatahub/client/filter/JSONGRDDLFilterProvider.java create mode 100644 src/main/java/com/atomgraph/linkeddatahub/client/filter/grddl/YouTubeGRDDLFilterProvider.java create mode 100644 src/main/resources/META-INF/services/com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilterProvider diff --git a/src/main/java/com/atomgraph/linkeddatahub/Application.java b/src/main/java/com/atomgraph/linkeddatahub/Application.java index 918b017fb..6e8991f1c 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/Application.java +++ b/src/main/java/com/atomgraph/linkeddatahub/Application.java @@ -70,7 +70,8 @@ import com.atomgraph.client.util.XsltResolver; import com.atomgraph.linkeddatahub.client.GraphStoreClient; import com.atomgraph.linkeddatahub.client.filter.ClientUriRewriteFilter; -import com.atomgraph.linkeddatahub.client.filter.grddl.YouTubeGRDDLFilter; +import com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilter; +import com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilterProvider; import com.atomgraph.linkeddatahub.imports.ImportExecutor; import com.atomgraph.linkeddatahub.io.HtmlJsonLDReaderFactory; import com.atomgraph.linkeddatahub.io.JsonLDReader; @@ -153,6 +154,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; +import java.util.ServiceLoader; import java.util.Map; import java.util.Properties; import jakarta.mail.Authenticator; @@ -1168,17 +1170,19 @@ protected void registerExceptionMappers() */ protected void registerClientFilters() { - try - { - // Register YouTube GRDDL filter - YouTubeGRDDLFilter youtubeFilter = new YouTubeGRDDLFilter(xsltComp); - client.register(youtubeFilter); - externalClient.register(youtubeFilter); - } - catch (SaxonApiException ex) + ServiceLoader.load(JSONGRDDLFilterProvider.class).forEach(provider -> { - if (log.isErrorEnabled()) log.error("Failed to initialize GRDDL client filter"); - } + try + { + JSONGRDDLFilter filter = provider.getFilter(xsltComp); + client.register(filter); + externalClient.register(filter); + } + catch (SaxonApiException ex) + { + if (log.isErrorEnabled()) log.error("Failed to initialize GRDDL client filter for {}", provider.getClass().getSimpleName()); + } + }); } /** diff --git a/src/main/java/com/atomgraph/linkeddatahub/client/filter/JSONGRDDLFilterProvider.java b/src/main/java/com/atomgraph/linkeddatahub/client/filter/JSONGRDDLFilterProvider.java new file mode 100644 index 000000000..9fa163c16 --- /dev/null +++ b/src/main/java/com/atomgraph/linkeddatahub/client/filter/JSONGRDDLFilterProvider.java @@ -0,0 +1,31 @@ +/** + * Copyright 2025 Martynas Jusevičius + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.atomgraph.linkeddatahub.client.filter; + +import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.XsltCompiler; + +/** + * SPI interface for providing {@link JSONGRDDLFilter} instances. + * Implementations are discovered via {@link java.util.ServiceLoader}. + */ +public interface JSONGRDDLFilterProvider +{ + + JSONGRDDLFilter getFilter(XsltCompiler xsltCompiler) throws SaxonApiException; + +} diff --git a/src/main/java/com/atomgraph/linkeddatahub/client/filter/grddl/YouTubeGRDDLFilterProvider.java b/src/main/java/com/atomgraph/linkeddatahub/client/filter/grddl/YouTubeGRDDLFilterProvider.java new file mode 100644 index 000000000..3bcb37a95 --- /dev/null +++ b/src/main/java/com/atomgraph/linkeddatahub/client/filter/grddl/YouTubeGRDDLFilterProvider.java @@ -0,0 +1,36 @@ +/** + * Copyright 2025 Martynas Jusevičius + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.atomgraph.linkeddatahub.client.filter.grddl; + +import com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilter; +import com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilterProvider; +import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.XsltCompiler; + +/** + * {@link JSONGRDDLFilterProvider} implementation for YouTube GRDDL transformation. + */ +public class YouTubeGRDDLFilterProvider implements JSONGRDDLFilterProvider +{ + + @Override + public JSONGRDDLFilter getFilter(XsltCompiler xsltCompiler) throws SaxonApiException + { + return new YouTubeGRDDLFilter(xsltCompiler); + } + +} diff --git a/src/main/resources/META-INF/services/com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilterProvider b/src/main/resources/META-INF/services/com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilterProvider new file mode 100644 index 000000000..d81a47a3f --- /dev/null +++ b/src/main/resources/META-INF/services/com.atomgraph.linkeddatahub.client.filter.JSONGRDDLFilterProvider @@ -0,0 +1 @@ +com.atomgraph.linkeddatahub.client.filter.grddl.YouTubeGRDDLFilterProvider From 3815c401912ca79c50ad2bfa6b54ec0e3c9e6fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 19 Mar 2026 12:31:49 +0100 Subject: [PATCH 5/5] make tests command --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9d598f80c..38b9fd026 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: sef drop cert release +.PHONY: sef drop cert release tests # Generate Saxon-JS SEF files for client-side XSLT transformations sef: @@ -10,8 +10,12 @@ drop: # Generate server SSL certificate using the .env config cert: - ./bin/server-cert-gen.sh .env nginx ssl + server-cert-gen.sh .env nginx ssl # Run the full Maven release process (prepare, deploy to Sonatype, merge to master/develop) release: ./release.sh + +# Run HTTP tests using owner and secretary certificates with passwords from secrets/ +tests: + cd http-tests && ./run.sh ../ssl/owner/cert.pem $$(cat ../secrets/owner_cert_password.txt) ../ssl/secretary/cert.pem $$(cat ../secrets/secretary_cert_password.txt)