From 929246370394bf1f79ec7f2f03353d09fa0c51ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 10 Jun 2026 00:05:50 +0200 Subject: [PATCH 01/14] [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 df0f49498..bcdd7851c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.atomgraph linkeddatahub - 5.5.3 + 5.5.4-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.5.3 + linkeddatahub-2.1.1 From ff479c32e4f2e4d48395fbc3256b5c46c76eb9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 17 Jun 2026 15:20:35 +0200 Subject: [PATCH 02/14] Add regression test for Varnish cache poisoning via On-Behalf-Of delegation Proxy server-side fetches set On-Behalf-Of (WebIDDelegationFilter), and the backend response carries the asserted agent's WebID in the Link header. The URL-keyed varnish-frontend entry then replays that response to subsequent anonymous requests for the same URL+Accept. Test fires the proxy request as owner, then asserts that an anonymous direct request returns 403 and that no acl#agent appears in the Link header. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../proxy/GET-proxied-no-cache-poisoning.sh | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100755 http-tests/proxy/GET-proxied-no-cache-poisoning.sh diff --git a/http-tests/proxy/GET-proxied-no-cache-poisoning.sh b/http-tests/proxy/GET-proxied-no-cache-poisoning.sh new file mode 100755 index 000000000..e1467c34f --- /dev/null +++ b/http-tests/proxy/GET-proxied-no-cache-poisoning.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Regression: ProxyRequestFilter's server-side fetch attaches On-Behalf-Of +# (via WebIDDelegationFilter), and the backend response carries the asserted +# agent's WebID in the Link header (acl#agent). varnish-frontend must not +# cache that response under a URL-keyed entry — otherwise a subsequent +# anonymous request to the same URL+Accept replays the cached 200 and reads +# back the previous agent's identity (and inherits whatever ACL grant they +# had). + +purge_cache "$END_USER_VARNISH_SERVICE" +purge_cache "$ADMIN_VARNISH_SERVICE" +purge_cache "$FRONTEND_VARNISH_SERVICE" + +# Step A: authenticated owner fires a proxy request from the end-user +# dataspace to the admin dataspace. This triggers WebIDDelegationFilter → +# On-Behalf-Of on the server-side hop into varnish-frontend. + +curl -k -f -s -o /dev/null \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -G \ + -H 'Accept: application/rdf+xml' \ + --data-urlencode "uri=${ADMIN_BASE_URL}" \ + "${END_USER_BASE_URL}" + +# Step B: anonymous direct request to the admin URL with the same Accept. +# If the cache was poisoned in Step A, this returns 200 with the owner's +# WebID in the Link header. Expected after the fix: varnish-frontend should +# pass on On-Behalf-Of and store nothing, so this goes to the backend +# anonymously and gets 403. + +response=$(curl -k -s -i -H 'Accept: application/rdf+xml' "${ADMIN_BASE_URL}") + +status=$(printf '%s\n' "$response" | awk 'NR==1{print $2}' | tr -d '\r') +link_leak=$(printf '%s\n' "$response" | tr -d '\r' | grep -i '^link:' | grep -c 'acl#agent' || true) + +if [ "$status" != "$STATUS_FORBIDDEN" ]; then + echo "Expected $STATUS_FORBIDDEN anonymous, got: $status" + printf '%s\n' "$response" | head -40 + exit 1 +fi + +if [ "$link_leak" != "0" ]; then + echo "Anonymous response leaks acl#agent in Link header (cache poisoning):" + printf '%s\n' "$response" | grep -i '^link:' + exit 1 +fi From d937d8cc95ba888ef9f547755a3ae0f7babb937a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 17 Jun 2026 15:25:32 +0200 Subject: [PATCH 03/14] Bypass varnish-frontend cache for On-Behalf-Of requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proxy server-side fetches set On-Behalf-Of (WebIDDelegationFilter), and the backend response echoes the asserted agent's WebID in the Link header (acl#agent). The previous vcl_recv only passed for Client-Cert + HTML, so the delegated RDF response was stored under a key that ignored the asserted identity — any subsequent anonymous request to the same URL+Accept replayed the cached 200 and read back the previous agent's identity and ACL grant. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 02b5f5203..71f82f818 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -384,6 +384,15 @@ configs: return (pass); } + # Delegated requests carry user identity in the On-Behalf-Of header. + # The backend response echoes that identity in the Link header + # (acl#agent). The cache key does not include the asserted identity, + # so caching would let a later anonymous request to the same + # URL+Accept read back the previous agent's WebID and ACL grant. + if (req.http.On-Behalf-Of) { + return (pass); + } + if (req.http.Client-Cert) { # Authenticated HTML is user-specific → never cache if (req.http.Accept ~ "text/html" || From 72804d72be0a019e6496f1061a5cfb98b5848792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 17 Jun 2026 15:28:14 +0200 Subject: [PATCH 04/14] Show progress cursor while 3D graph requests are in flight Stamp body.cursor=progress when ldh:http-request is issued for the document RDF and backlinks fetches, and reset to default when the corresponding response handlers run, so the user gets feedback that the 3D graph is loading. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../linkeddatahub/xsl/bootstrap/2.3.2/client/graph3d.xsl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/graph3d.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/graph3d.xsl index bbfff9e59..3ccab5083 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/graph3d.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/graph3d.xsl @@ -122,6 +122,8 @@ WHERE 'graph-state': $graph-state }" as="map(*)"/> + + + + + + @@ -381,6 +387,8 @@ WHERE + + From cd7dd018c59345dfd993eccf3ea75dec0bb855c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Wed, 17 Jun 2026 15:28:55 +0200 Subject: [PATCH 05/14] Defer 'active' token on new tab panes to ldh:ActivateTab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newly-appended tab-body elements were defaulting to class="tab-pane active", which briefly left two panes carrying the 'active' token (the new one and the currently-active local one) between append and ldh:ActivateTab. Code running in that window — notably ldh:LeftSidebar, which reads ldt:base() and sd:endpoint() off the active pane — would see ambiguous state and crash. Pass class="tab-pane" explicitly so only ldh:ActivateTab promotes a pane to active. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl index ecf8eff43..ddf605df9 100644 --- a/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl +++ b/src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl @@ -559,8 +559,10 @@ WHERE + + @@ -718,6 +720,7 @@ WHERE + From 62656d74ce14b05c5d677e06e600e7b45f8e3be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 08:23:01 +0200 Subject: [PATCH 06/14] Extend cache-poisoning regression to the Client-Cert + RDF path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing Step A/B covered the proxy-delegation hop (On-Behalf-Of asserted in the HTTP header). It misses the symmetric path where the LDH server-side client presents its keystore cert at TLS to nginx: the backend stamps acl#agent into the Link header for the authenticated 200, and the URL-keyed cache slot ignores the asserter, so a later anonymous request reads the response back. Steps C/D drive that path directly — authenticated owner GET with Accept: application/rdf+xml, then anonymous direct GET, asserts 403 and no acl#agent in the Link header. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../proxy/GET-proxied-no-cache-poisoning.sh | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/http-tests/proxy/GET-proxied-no-cache-poisoning.sh b/http-tests/proxy/GET-proxied-no-cache-poisoning.sh index e1467c34f..2d4c2d130 100755 --- a/http-tests/proxy/GET-proxied-no-cache-poisoning.sh +++ b/http-tests/proxy/GET-proxied-no-cache-poisoning.sh @@ -46,3 +46,37 @@ if [ "$link_leak" != "0" ]; then printf '%s\n' "$response" | grep -i '^link:' exit 1 fi + +# Step C: authenticated owner fetches the admin URL directly with cert at TLS, +# Accept: application/rdf+xml. The Client-Cert header reaches varnish-frontend +# (nginx-forwarded). The backend stamps acl#agent into the Link header for the +# authenticated 200. varnish-frontend must NOT cache this response — its hash +# key ignores identity, so a subsequent anonymous request would replay the 200. + +purge_cache "$FRONTEND_VARNISH_SERVICE" + +curl -k -f -s -o /dev/null \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -H 'Accept: application/rdf+xml' \ + "${ADMIN_BASE_URL}" + +# Step D: anonymous direct fetch of the same URL. With the fix in place +# (Client-Cert + non-/static/ path → pass in vcl_recv), Step C didn't store +# anything, so this reaches the backend anonymously and gets 403. + +response=$(curl -k -s -i -H 'Accept: application/rdf+xml' "${ADMIN_BASE_URL}") + +status=$(printf '%s\n' "$response" | awk 'NR==1{print $2}' | tr -d '\r') +link_leak=$(printf '%s\n' "$response" | tr -d '\r' | grep -i '^link:' | grep -c 'acl#agent' || true) + +if [ "$status" != "$STATUS_FORBIDDEN" ]; then + echo "[Client-Cert path] Expected $STATUS_FORBIDDEN anonymous, got: $status" + printf '%s\n' "$response" | head -40 + exit 1 +fi + +if [ "$link_leak" != "0" ]; then + echo "[Client-Cert path] Anonymous response leaks acl#agent in Link header (cache poisoning):" + printf '%s\n' "$response" | grep -i '^link:' + exit 1 +fi From 699bddd2c74079d66073515157ddbe5394235b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 08:36:25 +0200 Subject: [PATCH 07/14] Bypass varnish-frontend cache for any Client-Cert request outside /static/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous Client-Cert block only passed for HTML and a few specific endpoints. RDF/Turtle/JSON-LD/SPARQL-results responses fell through to hash and got stored even though the backend stamps acl#agent into the Link header for every authenticated response, leaking that identity (and the asserter's ACL grant) to any anonymous reader of the same URL. The replacement rule is the actual semantic boundary: /static/* is served by Tomcat's default servlet (web.xml:365-371), bypasses Jersey, carries no identity in headers, and is safe to share across cache callers. Every other path is potentially user-specific when the request authenticates, so it must pass. The previously-listed If-Match / /access / /acl/agents/ sub-clauses are subsumed by Client-Cert + !/static/ → pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.yml | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 71f82f818..5f3522f1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -393,28 +393,15 @@ configs: return (pass); } - if (req.http.Client-Cert) { - # Authenticated HTML is user-specific → never cache - if (req.http.Accept ~ "text/html" || - req.http.Accept ~ "application/xhtml+xml") { - return (pass); - } - - # Conditional requests must reach backend for validation - if (req.http.If-Match || req.http.If-None-Match || - req.http.If-Modified-Since || req.http.If-Unmodified-Since) { - return (pass); - } - - # /access endpoint returns agent-specific group memberships - if (req.url ~ "^/access") { - return (pass); - } - - # SPARQL referencing /acl/agents/ depends on agent identity → don't cache - if (req.url ~ "%2Facl%2Fagents%2F") { - return (pass); - } + # Authenticated responses get acl#agent stamped into the Link header by + # the backend, regardless of representation (HTML, RDF/XML, Turtle, + # JSON-LD, SPARQL results, …). The URL-keyed cache slot ignores the + # asserting identity, so any such response would leak to anonymous + # readers of the same URL. /static/* is the only safely-shared path — + # it's served by Tomcat's default servlet (web.xml:365-371), bypasses + # Jersey, and carries no identity-bearing headers. + if (req.http.Client-Cert && req.url !~ "^/static/") { + return (pass); } if (req.http.Cookie) { From 5febdea770b1ef370676d0efa24beb22e118c864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 08:39:57 +0200 Subject: [PATCH 08/14] Trim cache-poisoning regression failure output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the response-body dumps and `[Client-Cert path]` tag prefixes — single echo + exit 1, matching the convention of sibling tests like GET-proxied-internal-403.sh. Step name still printed so a CI failure points at the right assertion. Co-Authored-By: Claude Opus 4.7 (1M context) --- http-tests/proxy/GET-proxied-no-cache-poisoning.sh | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/http-tests/proxy/GET-proxied-no-cache-poisoning.sh b/http-tests/proxy/GET-proxied-no-cache-poisoning.sh index 2d4c2d130..89337d238 100755 --- a/http-tests/proxy/GET-proxied-no-cache-poisoning.sh +++ b/http-tests/proxy/GET-proxied-no-cache-poisoning.sh @@ -36,14 +36,12 @@ status=$(printf '%s\n' "$response" | awk 'NR==1{print $2}' | tr -d '\r') link_leak=$(printf '%s\n' "$response" | tr -d '\r' | grep -i '^link:' | grep -c 'acl#agent' || true) if [ "$status" != "$STATUS_FORBIDDEN" ]; then - echo "Expected $STATUS_FORBIDDEN anonymous, got: $status" - printf '%s\n' "$response" | head -40 + echo "Step B: expected $STATUS_FORBIDDEN, got $status" exit 1 fi if [ "$link_leak" != "0" ]; then - echo "Anonymous response leaks acl#agent in Link header (cache poisoning):" - printf '%s\n' "$response" | grep -i '^link:' + echo "Step B: anonymous response leaks acl#agent (cache poisoning)" exit 1 fi @@ -70,13 +68,11 @@ status=$(printf '%s\n' "$response" | awk 'NR==1{print $2}' | tr -d '\r') link_leak=$(printf '%s\n' "$response" | tr -d '\r' | grep -i '^link:' | grep -c 'acl#agent' || true) if [ "$status" != "$STATUS_FORBIDDEN" ]; then - echo "[Client-Cert path] Expected $STATUS_FORBIDDEN anonymous, got: $status" - printf '%s\n' "$response" | head -40 + echo "Step D: expected $STATUS_FORBIDDEN, got $status" exit 1 fi if [ "$link_leak" != "0" ]; then - echo "[Client-Cert path] Anonymous response leaks acl#agent in Link header (cache poisoning):" - printf '%s\n' "$response" | grep -i '^link:' + echo "Step D: anonymous response leaks acl#agent (cache poisoning)" exit 1 fi From 9ec3b671d7c95780faadf902cd940a6071bef61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 09:41:42 +0200 Subject: [PATCH 09/14] Forward upstream validators for HEAD proxy responses The typed branches in getResponse() parse the upstream entity into a Model/ResultSet and re-stamp ETag/Last-Modified off the parsed value. For HEAD responses the body is empty by HTTP semantics, so that parse yields an empty Model/ResultSet whose hash-derived ETag disagrees with the GET ETag for the same URL. Previously masked by varnish-frontend: a GET-populated cache entry served HEAD requests with the original ETag, so the proxy's HEAD upstream fetch read body bytes from cache and the re-stamped validator happened to match direct. After widening the Client-Cert bypass in vcl_recv, both fetches go fresh to backend and the proxy's empty-body parse is exercised for real, producing a wrong ETag. For HEAD, skip the typed branches entirely and forward the upstream's ETag/Last-Modified verbatim via overlayHeaders(copyValidators=true). The proxy's response now agrees with the direct response. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../filter/request/ProxyRequestFilter.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java index e42e43a93..b2e56aa22 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java @@ -50,6 +50,7 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.EntityTag; @@ -240,7 +241,7 @@ else if (agentContext instanceof IDTokenSecurityContext idTokenSecurityContext) try (clientResponse) { - requestContext.abortWith(getResponse(clientResponse, targetURI)); + requestContext.abortWith(getResponse(clientResponse, targetURI, requestContext.getMethod())); } } catch (MessageBodyProviderNotFoundException ex) @@ -322,8 +323,20 @@ protected Optional resolveTargetURI(ContainerRequestContext requestContext) * @param targetURI upstream URI (used as the parse base URI hint for {@code ModelProvider}) * @return JAX-RS response to return to the original caller */ - protected Response getResponse(Response clientResponse, URI targetURI) + protected Response getResponse(Response clientResponse, URI targetURI, String method) { + // HEAD responses have no body by HTTP semantics. Routing them through + // the typed branches below would parse an empty entity into an empty + // Model/ResultSet, then re-stamp ETag/Last-Modified off that empty + // value — producing validators that disagree with the upstream GET. + // Forward the upstream headers (including ETag) verbatim instead. + if (HttpMethod.HEAD.equalsIgnoreCase(method)) + { + Response.ResponseBuilder rb = Response.status(clientResponse.getStatus()); + if (clientResponse.getMediaType() != null) rb.type(clientResponse.getMediaType()); + return overlayHeaders(rb.build(), clientResponse, true); + } + if (clientResponse.getMediaType() == null) { Response.ResponseBuilder rb = Response.status(clientResponse.getStatus()); From 2b2cc6700f25c66186c8d91ad0e68389e894f6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 09:59:05 +0200 Subject: [PATCH 10/14] Javadoc fix --- .../server/filter/request/ProxyRequestFilter.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java index b2e56aa22..514a423f1 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java @@ -275,9 +275,7 @@ protected Optional resolveTargetURI(ContainerRequestContext requestContext) if (proxyTarget != null) return Optional.of(proxyTarget); // Case 2: lapp:Dataset proxy - @SuppressWarnings("unchecked") - Optional datasetOpt = - (Optional) requestContext.getProperty(LAPP.Dataset.getURI()); + Optional datasetOpt = (Optional) requestContext.getProperty(LAPP.Dataset.getURI()); if (datasetOpt != null && datasetOpt.isPresent()) { URI proxied = datasetOpt.get().getProxied(requestContext.getUriInfo().getAbsolutePath()); @@ -321,6 +319,7 @@ protected Optional resolveTargetURI(ContainerRequestContext requestContext) * * @param clientResponse response from the proxy target * @param targetURI upstream URI (used as the parse base URI hint for {@code ModelProvider}) + * @param method HTTP method * @return JAX-RS response to return to the original caller */ protected Response getResponse(Response clientResponse, URI targetURI, String method) From a6d71b55b9c6840f44d6190e0165a7b51b79b9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 10:37:04 +0200 Subject: [PATCH 11/14] Use ParameterizedSparqlString for proxy namespace DESCRIBE query Bind the target URI via setIri() instead of string-concatenating it into the query text, eliminating the SPARQL injection surface on a URI containing metacharacters. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../server/filter/request/ProxyRequestFilter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java index 514a423f1..4eeb6599e 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java @@ -38,8 +38,8 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import org.apache.jena.query.ParameterizedSparqlString; import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryFactory; import jakarta.annotation.Priority; import jakarta.inject.Inject; import jakarta.ws.rs.NotAllowedException; @@ -192,9 +192,10 @@ public void filter(ContainerRequestContext requestContext) throws IOException // ?term where STR(?term) starts with "#") if (isSafeMethod && getOntology().isPresent()) { - String describeQueryStr = "DESCRIBE <" + targetURI + "> ?term " + - "WHERE { ?term ?p ?o FILTER(STRSTARTS(STR(?term), CONCAT(STR(<" + targetURI + ">), \"#\"))) }"; - try (QueryExecution qe = QueryExecution.create(QueryFactory.create(describeQueryStr), getOntology().get().getOntModel())) + ParameterizedSparqlString pss = new ParameterizedSparqlString( + "DESCRIBE ?doc ?term WHERE { ?term ?p ?o FILTER(STRSTARTS(STR(?term), CONCAT(STR(?doc), \"#\"))) }"); + pss.setIri("doc", targetURI.toString()); + try (QueryExecution qe = QueryExecution.create(pss.asQuery(), getOntology().get().getOntModel())) { Model description = qe.execDescribe(); if (!description.isEmpty()) From 1bff73940e896489f9b43e0d1c0a30ddea0a7f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 18 Jun 2026 10:54:07 +0200 Subject: [PATCH 12/14] Add unit tests for graph store, RDF import streaming, and proxied WebID auth DocumentHierarchyGraphStoreImplTest covers the stateless logic (getChangedResources, getLastModified, writeFile/writeFiles guards and file I/O) via a CALLS_REAL_METHODS mock, since the class only exposes a heavyweight @Inject constructor. StreamRDFOutputWriterTest covers the RDF-import streaming path: null guard, getter round-trip, and the non-RDF media type -> BadRequestException branch. The CSV transformation itself is covered upstream in CSV2RDF. ProxiedWebIDFilterTest covers Client-Cert header PEM parsing into an X509Certificate, absent header, and malformed input. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../stream/StreamRDFOutputWriterTest.java | 100 +++++++ .../request/auth/ProxiedWebIDFilterTest.java | 115 ++++++++ .../DocumentHierarchyGraphStoreImplTest.java | 274 ++++++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 src/test/java/com/atomgraph/linkeddatahub/imports/stream/StreamRDFOutputWriterTest.java create mode 100644 src/test/java/com/atomgraph/linkeddatahub/server/filter/request/auth/ProxiedWebIDFilterTest.java create mode 100644 src/test/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImplTest.java diff --git a/src/test/java/com/atomgraph/linkeddatahub/imports/stream/StreamRDFOutputWriterTest.java b/src/test/java/com/atomgraph/linkeddatahub/imports/stream/StreamRDFOutputWriterTest.java new file mode 100644 index 000000000..0e3f2b6cb --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/imports/stream/StreamRDFOutputWriterTest.java @@ -0,0 +1,100 @@ +/** + * 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.imports.stream; + +import com.atomgraph.linkeddatahub.client.GraphStoreClient; +import com.atomgraph.linkeddatahub.model.Service; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link StreamRDFOutputWriter}. + * + * @author Martynas Jusevičius {@literal } + */ +@ExtendWith(MockitoExtension.class) +public class StreamRDFOutputWriterTest +{ + + @Mock private Service service; + @Mock private Service adminService; + @Mock private com.atomgraph.linkeddatahub.Application system; + @Mock private GraphStoreClient gsc; + + private static final String BASE_URI = "http://localhost/"; + private static final String GRAPH_URI = "http://localhost/graphs/import"; + + private Query query; + private StreamRDFOutputWriter writer; + + @BeforeEach + public void setUp() + { + query = QueryFactory.create("CONSTRUCT WHERE { ?s ?p ?o }"); + writer = new StreamRDFOutputWriter(service, adminService, system, gsc, BASE_URI, query, GRAPH_URI); + } + + @Test + public void testGettersRoundTrip() + { + assertSame(service, writer.getService()); + assertSame(adminService, writer.getAdminService()); + assertSame(system, writer.getSystem()); + assertSame(gsc, writer.getGraphStoreClient()); + assertEquals(BASE_URI, writer.getBaseURI()); + assertSame(query, writer.getQuery()); + assertEquals(GRAPH_URI, writer.getGraphURI()); + } + + @Test + public void testApplyNullResponse() + { + assertThrows(IllegalArgumentException.class, () -> writer.apply(null)); + } + + /** + * A response whose {@code Content-Type} does not map to an RDF language must be rejected with + * {@code 400 Bad Request} — after the body has been buffered to a temp file but before any + * Graph Store write is attempted. + */ + @Test + public void testApplyNonRdfMediaTypeThrowsBadRequest() + { + Response response = mock(Response.class); + lenient().when(response.readEntity(InputStream.class)).thenReturn((InputStream)new ByteArrayInputStream("not rdf".getBytes())); + when(response.getMediaType()).thenReturn(MediaType.valueOf("image/png")); + + assertThrows(BadRequestException.class, () -> writer.apply(response)); + } + +} diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/auth/ProxiedWebIDFilterTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/auth/ProxiedWebIDFilterTest.java new file mode 100644 index 000000000..e23a32861 --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/auth/ProxiedWebIDFilterTest.java @@ -0,0 +1,115 @@ +/** + * 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.server.filter.request.auth; + +import jakarta.ws.rs.container.ContainerRequestContext; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link ProxiedWebIDFilter}, which extracts the client certificate from the + * {@code Client-Cert} request header (URL-encoded PEM) instead of the TLS connection. + * + * @author Martynas Jusevičius {@literal } + */ +@ExtendWith(MockitoExtension.class) +public class ProxiedWebIDFilterTest +{ + + private static final String CLIENT_CERT_HEADER_NAME = "Client-Cert"; + + /** A self-signed X.509 certificate (CN=Test WebID, O=LinkedDataHub) in PEM form. */ + private static final String CERT_PEM = + "-----BEGIN CERTIFICATE-----\n" + + "MIIC1jCCAb4CCQD35LqgLKP0ATANBgkqhkiG9w0BAQsFADAtMRMwEQYDVQQDDApU\n" + + "ZXN0IFdlYklEMRYwFAYDVQQKDA1MaW5rZWREYXRhSHViMB4XDTI2MDYxODA4Mzkz\n" + + "OFoXDTI2MDYxOTA4MzkzOFowLTETMBEGA1UEAwwKVGVzdCBXZWJJRDEWMBQGA1UE\n" + + "CgwNTGlua2VkRGF0YUh1YjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" + + "AMRsMfh0IikmxDbZ0kca2FRI+WXelczdi+dU9ZC42cAZJM6pEu9icvCdTKBKipYE\n" + + "07PkfAgsmkS3qzly1iQzyXZFzopndg9FdFvWZyn8SdxNSKCcQt13NTp8sXflkdxK\n" + + "SfOseUx1cZ0T4ylGNwkqxcqZo5b06CJqiZqjgO4x7kYWWrli44AgzMkT3AgJqq5X\n" + + "iSo5j8gOjicR+ZywLAEvWH0ITja4sIgsQzZHxbquOuPEevnT+135M33wHxsY5MHJ\n" + + "Ykxid7C4ifVm4jXf81CmnoCifR9UeBnMZ0QBPBP/Exv+CpTgb3TBAfF1o1QsEuM3\n" + + "60fYmqLcwLiKgZqDJ7ZH80UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAeYJGVnFq\n" + + "CARK15JQQk1YBAUPspkFWAeXH9UzYyxpqt0bLlYO9g4KExJVJvE9Qub2lHXBs36j\n" + + "/elRF+PR5Zt/6LD26OnSu+QWkFSqbO6Otul7g9ikMufuhNrZyyOOzidqFfcfkhWx\n" + + "FZh+yZhGoo2f+ddMuYbK3lKI+/DMswfdNN6VN++EOYskjWBB85GKUxEJTLEF2yE+\n" + + "yRtqnQfX3ucvO2Zd1XHsgknzoSfG8CXZF3GDcqzzEZ6Aa//xtwYRCmNmj9E9SdMY\n" + + "xuCHnQP3cV/vBBhxt1BWdIRtcU6xpasNMfWGgAxqrCTz+GnT7FExbe5qt6CgX7yl\n" + + "JLw8c9VNQzsM9g==\n" + + "-----END CERTIFICATE-----\n"; + + @Mock private ContainerRequestContext requestContext; + + private ProxiedWebIDFilter filter; + + @BeforeEach + public void setUp() + { + filter = new ProxiedWebIDFilter(); + } + + @Test + public void testCertificateFactoryIsX509() + { + assertNotNull(filter.getCertificateFactory()); + assertEquals("X.509", filter.getCertificateFactory().getType()); + } + + /** No {@code Client-Cert} header — no certificate. */ + @Test + public void testNoHeaderReturnsNull() throws Exception + { + when(requestContext.getHeaderString(CLIENT_CERT_HEADER_NAME)).thenReturn(null); + assertNull(filter.getWebIDCertificate(requestContext)); + } + + /** A URL-encoded PEM certificate in the header is decoded and parsed into an X509Certificate. */ + @Test + public void testValidHeaderParsesCertificate() throws Exception + { + String encoded = URLEncoder.encode(CERT_PEM, StandardCharsets.UTF_8); + when(requestContext.getHeaderString(CLIENT_CERT_HEADER_NAME)).thenReturn(encoded); + + X509Certificate cert = filter.getWebIDCertificate(requestContext); + + assertNotNull(cert); + assertTrue(cert.getSubjectX500Principal().getName().contains("Test WebID")); + } + + /** A header that is not a valid certificate must surface as a CertificateException. */ + @Test + public void testMalformedHeaderThrows() + { + when(requestContext.getHeaderString(CLIENT_CERT_HEADER_NAME)).thenReturn("garbage-not-a-cert"); + assertThrows(CertificateException.class, () -> filter.getWebIDCertificate(requestContext)); + } + +} diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImplTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImplTest.java new file mode 100644 index 000000000..de95fa289 --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImplTest.java @@ -0,0 +1,274 @@ +/** + * 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.server.model.impl; + +import com.atomgraph.linkeddatahub.vocabulary.SIOC; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Set; +import org.apache.jena.datatypes.xsd.XSDDateTime; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.vocabulary.DCTerms; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for the stateless logic of {@link DocumentHierarchyGraphStoreImpl}. + *

+ * The class has a single heavyweight {@code @Inject} constructor that wires the JAX-RS request + * context and an HTTP-backed {@code GraphStoreClient}, so it cannot be instantiated directly in a + * unit test. The methods exercised here ({@code getChangedResources}, {@code getLastModified}, and + * the {@code writeFile}/{@code writeFiles} guards and file I/O) read no instance state, so we obtain + * an instance via Mockito's {@code CALLS_REAL_METHODS} (which skips the constructor) and invoke the + * real method bodies. + * + * @author Martynas Jusevičius {@literal } + */ +public class DocumentHierarchyGraphStoreImplTest +{ + + private final DocumentHierarchyGraphStoreImpl gs = mock(DocumentHierarchyGraphStoreImpl.class, CALLS_REAL_METHODS); + + // getChangedResources() + + @Test + public void testChangedResourcesNullBefore() + { + assertThrows(IllegalArgumentException.class, () -> gs.getChangedResources(null, ModelFactory.createDefaultModel())); + } + + @Test + public void testChangedResourcesNullAfter() + { + assertThrows(IllegalArgumentException.class, () -> gs.getChangedResources(ModelFactory.createDefaultModel(), null)); + } + + @Test + public void testChangedResourcesDetectsAddedSubject() + { + Model before = ModelFactory.createDefaultModel(); + Model after = ModelFactory.createDefaultModel(); + Resource added = after.createResource("http://localhost/added"); + added.addProperty(SIOC.HAS_PARENT, after.createResource("http://localhost/")); + + Set changed = gs.getChangedResources(before, after); + assertTrue(changed.contains(added)); + } + + @Test + public void testChangedResourcesDetectsRemovedSubject() + { + Model before = ModelFactory.createDefaultModel(); + Resource removed = before.createResource("http://localhost/removed"); + removed.addProperty(SIOC.HAS_PARENT, before.createResource("http://localhost/")); + Model after = ModelFactory.createDefaultModel(); + + Set changed = gs.getChangedResources(before, after); + assertTrue(changed.contains(removed)); + } + + @Test + public void testChangedResourcesIdenticalModelsAreEmpty() + { + Model before = ModelFactory.createDefaultModel(); + before.createResource("http://localhost/doc").addProperty(DCTerms.creator, before.createResource("http://localhost/agent")); + Model after = ModelFactory.createDefaultModel().add(before); + + assertTrue(gs.getChangedResources(before, after).isEmpty()); + } + + // getLastModified(Resource) + + @Test + public void testLastModifiedNullResource() + { + assertThrows(IllegalArgumentException.class, () -> gs.getLastModified((Resource)null)); + } + + @Test + public void testLastModifiedReturnsNullWhenNoDates() + { + Model model = ModelFactory.createDefaultModel(); + Resource resource = model.createResource("http://localhost/doc"); + assertNull(gs.getLastModified(resource)); + } + + @Test + public void testLastModifiedReturnsMaxOfCreatedAndModified() + { + Model model = ModelFactory.createDefaultModel(); + Calendar createdCal = new GregorianCalendar(2020, Calendar.JANUARY, 1, 0, 0, 0); + Calendar modifiedCal = new GregorianCalendar(2021, Calendar.JANUARY, 1, 0, 0, 0); + Resource resource = model.createResource("http://localhost/doc"). + addProperty(DCTerms.created, model.createTypedLiteral(createdCal)). + addProperty(DCTerms.modified, model.createTypedLiteral(modifiedCal)); + + // expected value is the later (modified) date, round-tripped through the same XSDDateTime path the method uses + Date expected = ((XSDDateTime)model.createTypedLiteral(modifiedCal).getValue()).asCalendar().getTime(); + assertEquals(expected, gs.getLastModified(resource)); + } + + @Test + public void testLastModifiedIgnoresNonDateTimeLiterals() + { + Model model = ModelFactory.createDefaultModel(); + Resource resource = model.createResource("http://localhost/doc"). + addProperty(DCTerms.modified, "not a date"); // plain string literal, not xsd:dateTime + assertNull(gs.getLastModified(resource)); + } + + // getLastModified(Model, URI) + + @Test + public void testLastModifiedByGraphURIReturnsNullForNullURI() + { + assertNull(gs.getLastModified(ModelFactory.createDefaultModel(), null)); + } + + @Test + public void testLastModifiedByGraphURI() + { + Model model = ModelFactory.createDefaultModel(); + Calendar cal = new GregorianCalendar(2022, Calendar.MARCH, 15, 12, 0, 0); + URI graphURI = URI.create("http://localhost/doc"); + model.createResource(graphURI.toString()).addProperty(DCTerms.modified, model.createTypedLiteral(cal)); + + Date expected = ((XSDDateTime)model.createTypedLiteral(cal).getValue()).asCalendar().getTime(); + assertEquals(expected, gs.getLastModified(model, graphURI)); + } + + // writeFile(File, InputStream) + + @Test + public void testWriteFileNullFile() + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFile((File)null, new ByteArrayInputStream(new byte[0]))); + } + + @Test + public void testWriteFileNullInputStream(@TempDir Path tempDir) + { + File file = tempDir.resolve("out.bin").toFile(); + assertThrows(IllegalArgumentException.class, () -> gs.writeFile(file, (InputStream)null)); + } + + @Test + public void testWriteFileWritesContent(@TempDir Path tempDir) throws Exception + { + File file = tempDir.resolve("out.bin").toFile(); + byte[] data = "hello world".getBytes(StandardCharsets.UTF_8); + + gs.writeFile(file, new ByteArrayInputStream(data)); + + assertTrue(file.exists()); + assertArrayEquals(data, Files.readAllBytes(file.toPath())); + } + + // writeFile(URI, URI, URI, InputStream) + + @Test + public void testWriteFileByURINullURI(@TempDir Path tempDir) + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFile(null, URI.create("http://localhost/"), tempDir.toUri(), new ByteArrayInputStream(new byte[0]))); + } + + @Test + public void testWriteFileByURIRelativeURIRejected(@TempDir Path tempDir) + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFile(URI.create("relative/path"), URI.create("http://localhost/"), tempDir.toUri(), new ByteArrayInputStream(new byte[0]))); + } + + @Test + public void testWriteFileByURINullBase(@TempDir Path tempDir) + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFile(URI.create("http://localhost/myfile"), null, tempDir.toUri(), new ByteArrayInputStream(new byte[0]))); + } + + @Test + public void testWriteFileByURINullUploadRoot() + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFile(URI.create("http://localhost/myfile"), URI.create("http://localhost/"), null, new ByteArrayInputStream(new byte[0]))); + } + + @Test + public void testWriteFileByURIResolvesRelativePath(@TempDir Path tempDir) throws Exception + { + URI base = URI.create("http://localhost/"); + URI uri = URI.create("http://localhost/myfile"); + URI uploadRoot = tempDir.toUri(); // ends with '/' + byte[] data = "content-addressed".getBytes(StandardCharsets.UTF_8); + + File written = gs.writeFile(uri, base, uploadRoot, new ByteArrayInputStream(data)); + + assertEquals(new File(uploadRoot.resolve("myfile")), written); + assertArrayEquals(data, Files.readAllBytes(written.toPath())); + } + + // writeFiles(Model, Map) + + @Test + public void testWriteFilesNullModel() + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFiles(null, new HashMap<>())); + } + + @Test + public void testWriteFilesNullMap() + { + assertThrows(IllegalArgumentException.class, () -> gs.writeFiles(ModelFactory.createDefaultModel(), null)); + } + + @Test + public void testWriteFilesNoFileResourcesWritesNothing() + { + Model model = ModelFactory.createDefaultModel(); + model.createResource("http://localhost/doc").addProperty(DCTerms.creator, model.createResource("http://localhost/agent")); + + assertEquals(0, gs.writeFiles(model, new HashMap<>())); + } + + @Test + public void testChangedResourcesUnchangedDoesNotContainSubject() + { + Model before = ModelFactory.createDefaultModel(); + Resource subject = before.createResource("http://localhost/doc"); + subject.addProperty(DCTerms.creator, before.createResource("http://localhost/agent")); + Model after = ModelFactory.createDefaultModel().add(before); + + assertFalse(gs.getChangedResources(before, after).contains(subject)); + } + +} From 997da5233a6e19c416c73e40c676267407151016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sun, 21 Jun 2026 11:30:33 +0200 Subject: [PATCH 13/14] Jena 6 ont-api (#316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Migrate vocabularies to Jena ontapi; add ontology characterization tests - Bump client dependency to 4.3.1-SNAPSHOT (pulls jena-ontapi transitively via Core) - Migrate 19 vocabularies off the deprecated-for-removal org.apache.jena.ontology API to org.apache.jena.ontapi (OntModelFactory/OntSpecification), with a top-of-class static JenaSystem.init() guard (ontapi NPEs if a vocab class is the first Jena touch) - Add characterization tests pinning current ontology behavior so the migration can be proven to retain it: imports closure + RDFS materialization, dual-key cache (addDocumentModel), SPARQL-first resolution (OntologyModelGetter), vocab triples Co-Authored-By: Claude Opus 4.8 (1M context) * Phase D: dissolve DataManager + migrate ontology pipeline to ontapi Completes the dissolution in LinkedDataHub (0 [removal] warnings, was ~90): - DataManagerImpl -> SameSiteSourceResolver (RDFSourceResolver subclass, same-site restriction) - OntologyModelGetter -> OntologyRepository (SPARQL-first PrefixGraphRepository subclass) - DataManagerFactory -> SourceResolverFactory (provides request-scoped RDFSourceResolver) - Application: per-app OntModelSpec/OntDocumentManager -> per-app OntologyRepository; global repository + resolver; remove PrefixMapper/LocationMapper/OntDocumentManager wiring - OntologyFilter/ClearOntology: ontapi OntModelFactory.createModel with OWL2_DL_MEM_RDFS_INF + materialize into OWL2_DL_MEM cached in the repository; dual-key import-closure caching preserved - request ontology property Optional -> Optional (ontapi) across OntologyFactory + ~8 consumers (.getOntModel() dropped) - Validator, ProxyRequestFilter, ValidatingModelProvider, Install/UninstallPackage: DataManager/OntModelSpec cache ops -> repository - characterization tests adapted to new API (OntologyRepositoryTest, OntologyFilterTest); 69 tests green Co-Authored-By: Claude Opus 4.8 (1M context) * Fix OntologyFilter: flatten owl:imports closure manually, not via ontapi union-graph repository OntModelFactory.createModel(graph, spec, repository) registers graphs in an OntUnionGraphRepository keyed by ontology ID, which collided with the same graph already in our flat PrefixGraphRepository cache (OntJenaException: 'Another graph with name <...#> is already in the hierarchy', thrown at runtime on the first /ns request). Replace it with explicit owl:imports closure traversal (loadClosure) that unions the base + imports into one graph, runs RDFS inference over that, and materializes — so createModel is only ever called WITHOUT a repository (no union-hierarchy registration). Encapsulates the duplicated OntologyFilter/ClearOntology load logic into OntologyFilter.loadOntology. OntologyImportsCharacterizationTest now exercises loadOntology and asserts the materialized closure. Co-Authored-By: Claude Opus 4.8 (1M context) * Fix empty constructed response: use OWL1_FULL_MEM so rdfs:Class is recognized GET /ns?forClass=...#Item returned an empty graph: dh:Item is declared 'a rdfs:Class' (dh.ttl), and the ontapi OWL2_DL_MEM profile I'd used does not recognize rdfs:Class as an OntClass, so Namespace's getOntClass(uri) returned null and no SPIN constructor ran. Legacy OntModelSpec.OWL_MEM is OWL 1 Full (recognizes rdfs:Class); the correct ontapi mapping is OWL1_FULL_MEM, not OWL2_DL_MEM. Applied across OntologyFilter pipeline + all vocab holders. OntologyImportsCharacterizationTest now guards getOntClass(rdfs:Class) != null. Co-Authored-By: Claude Opus 4.8 (1M context) * Use twirl 1.2.0-SNAPSHOT; drop redundant SPIN personality registration twirl 1.2.0 re-bases the constraint model onto its own SPIN personality, so the ontapi ontology model can be passed straight to SPINConstraints.check; LDH no longer needs to register SPIN into the global personality. - twirl 1.1.0 -> 1.2.0-SNAPSHOT - Remove SP.init(BuiltinPersonalities.model) from Application (Web-Client Constructor reads sp:text directly; nothing else needed the global registration) - Add SPINConstraintValidationTest: guards the Validator path and the raw-ontapi-via-twirl-rebase path Co-Authored-By: Claude Opus 4.8 (1M context) * ci: re-run against deployed twirl/core snapshots * ci: re-run against deployed twirl/core snapshots * Normalize ontologies to owl:Class; use OWL2_FULL_MEM profile The document/LDT/ACL ontology classes were declared bare rdfs:Class, which no OWL2 ontapi profile recognizes as an OntClass — forcing OWL1_FULL_MEM (which in turn bans named individuals, breaking SD). Declare them owl:Class (dual with rdfs:Class) and switch all OntSpecification usages from OWL1_FULL_MEM to OWL2_FULL_MEM, so getOntClass/forClass works and named individuals are allowed. - dh.ttl/ldt.ttl/lacl.ttl: class declarations now 'a rdfs:Class, owl:Class' - vocab holders + OntologyFilter + ConstructForClass: OWL1_FULL_MEM -> OWL2_FULL_MEM - remove dead 'import com.atomgraph.client.locator.PrefixMapper' (deleted in Web-Client) - OntologyImportsCharacterizationTest: assert owl:Class (not rdfs:Class) is recognized Co-Authored-By: Claude Opus 4.8 (1M context) * Add debug output to add-property-constraint and GET-proxied-ontology-ns tests Co-Authored-By: Claude Opus 4.8 (1M context) * Removed debug output * Tests for RDFS ontology imports * Promote rdfs:Class to owl:Class during ontology materialization OWL2 profiles do not recognise bare rdfs:Class as OntClass, so third-party vocab terms declared only as rdfs:Class (e.g. sp:Describe in sp.ttl) were invisible to getOntClass() lookups, returning empty forClass constructor responses. Co-Authored-By: Claude Opus 4.8 (1M context) * Repoint PrefixGraphRepository import to Web-Client; replace deprecated Model.write with RDFWriter - Update imports to com.atomgraph.client.util.jena.PrefixGraphRepository (moved out of Core): Application, OntologyFilter, OntologyRepository, SourceResolverFactory, SameSiteSourceResolver + tests - XSLTWriterBase: use RDFWriter instead of deprecated Model.write, keeping plain RDF/XML Co-Authored-By: Claude Opus 4.8 (1M context) * SNAPSHOT bump --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .../GET-namespace-forClass-rdfs.sh | 22 ++ pom.xml | 8 +- .../atomgraph/linkeddatahub/Application.java | 155 ++++++------- .../linkeddatahub/resource/Generate.java | 10 +- .../linkeddatahub/resource/Namespace.java | 18 +- .../resource/admin/ClearOntology.java | 26 +-- .../linkeddatahub/resource/admin/SignUp.java | 4 +- .../resource/admin/pkg/InstallPackage.java | 34 +-- .../resource/admin/pkg/UninstallPackage.java | 25 +-- .../server/event/AuthorizationCreated.java | 1 - .../server/factory/OntologyFactory.java | 12 +- .../server/filter/request/OntologyFilter.java | 154 ++++++------- .../filter/request/ProxyRequestFilter.java | 12 +- .../server/io/ValidatingModelProvider.java | 2 +- .../impl/DocumentHierarchyGraphStoreImpl.java | 8 +- ...delGetter.java => OntologyRepository.java} | 74 +++---- .../linkeddatahub/vocabulary/ACL.java | 45 ++-- .../linkeddatahub/vocabulary/Admin.java | 14 +- .../linkeddatahub/vocabulary/Cert.java | 28 ++- .../linkeddatahub/vocabulary/DH.java | 26 ++- .../linkeddatahub/vocabulary/Default.java | 17 +- .../linkeddatahub/vocabulary/FOAF.java | 46 ++-- .../linkeddatahub/vocabulary/Google.java | 20 +- .../linkeddatahub/vocabulary/LACL.java | 38 ++-- .../linkeddatahub/vocabulary/LAPP.java | 46 ++-- .../linkeddatahub/vocabulary/LDH.java | 56 ++--- .../linkeddatahub/vocabulary/LDHC.java | 107 ++++----- .../linkeddatahub/vocabulary/LDHT.java | 21 +- .../linkeddatahub/vocabulary/NFO.java | 21 +- .../linkeddatahub/vocabulary/ORCID.java | 20 +- .../linkeddatahub/vocabulary/PROV.java | 38 ++-- .../linkeddatahub/vocabulary/SIOC.java | 206 +++++++++--------- .../linkeddatahub/vocabulary/VoID.java | 28 ++- .../linkeddatahub/writer/ModelXSLTWriter.java | 7 +- .../writer/ResultSetXSLTWriter.java | 7 +- .../linkeddatahub/writer/XSLTWriterBase.java | 46 ++-- ...actory.java => SourceResolverFactory.java} | 47 ++-- ...rImpl.java => SameSiteSourceResolver.java} | 72 ++---- .../server/io/ValidatingDatasetProvider.java | 10 +- .../server/io/ValidatingModelProvider.java | 10 +- .../com/atomgraph/server/util/Validator.java | 2 +- .../com/atomgraph/server/vocabulary/HTTP.java | 30 +-- .../com/atomgraph/server/vocabulary/LDT.java | 64 +++--- .../linkeddatahub/app/admin/lacl.ttl | 4 +- .../com/atomgraph/linkeddatahub/dh.ttl | 6 +- .../com/atomgraph/linkeddatahub/ldt.ttl | 14 +- .../filter/request/OntologyFilterTest.java | 68 ++++++ .../OntologyImportsCharacterizationTest.java | 91 ++++++++ .../server/util/OntologyRepositoryTest.java | 111 ++++++++++ .../util/SPINConstraintValidationTest.java | 98 +++++++++ .../vocabulary/VocabularyHolderTest.java | 63 ++++++ 51 files changed, 1264 insertions(+), 828 deletions(-) create mode 100644 http-tests/document-hierarchy/GET-namespace-forClass-rdfs.sh rename src/main/java/com/atomgraph/linkeddatahub/server/util/{OntologyModelGetter.java => OntologyRepository.java} (64%) rename src/main/java/com/atomgraph/linkeddatahub/writer/factory/{DataManagerFactory.java => SourceResolverFactory.java} (70%) rename src/main/java/com/atomgraph/linkeddatahub/writer/impl/{DataManagerImpl.java => SameSiteSourceResolver.java} (59%) create mode 100644 src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilterTest.java create mode 100644 src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyImportsCharacterizationTest.java create mode 100644 src/test/java/com/atomgraph/linkeddatahub/server/util/OntologyRepositoryTest.java create mode 100644 src/test/java/com/atomgraph/linkeddatahub/server/util/SPINConstraintValidationTest.java create mode 100644 src/test/java/com/atomgraph/linkeddatahub/vocabulary/VocabularyHolderTest.java diff --git a/http-tests/document-hierarchy/GET-namespace-forClass-rdfs.sh b/http-tests/document-hierarchy/GET-namespace-forClass-rdfs.sh new file mode 100644 index 000000000..e363dfbcf --- /dev/null +++ b/http-tests/document-hierarchy/GET-namespace-forClass-rdfs.sh @@ -0,0 +1,22 @@ +#!/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" + +# sp:Describe is declared only as rdfs:Class (not owl:Class) in sp.ttl. +# OntologyFilter must promote rdfs:Class to owl:Class during materialization so +# that OWL2 profiles recognise third-party vocab terms and return their SPIN constructors. + +response=$(curl -k -f -s \ + -G \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -H "Accept: application/rdf+xml" \ + --data-urlencode "forClass=http://spinrdf.org/sp#Describe" \ + "${END_USER_BASE_URL}ns") + +# response must be non-empty: sp:Describe must be recognised as an OntClass +echo "$response" | grep -q "http://spinrdf.org/sp#Describe" diff --git a/pom.xml b/pom.xml index bcdd7851c..f8fb12086 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.atomgraph linkeddatahub - 5.5.4-SNAPSHOT + 5.6.0-SNAPSHOT ${packaging.type} AtomGraph LinkedDataHub @@ -146,7 +146,7 @@ ${project.groupId} twirl - 1.1.0 + 1.2.0-SNAPSHOT @@ -163,13 +163,13 @@ ${project.groupId} client - 4.3.0 + 4.4.0-SNAPSHOT classes ${project.groupId} client - 4.3.0 + 4.4.0-SNAPSHOT war diff --git a/src/main/java/com/atomgraph/linkeddatahub/Application.java b/src/main/java/com/atomgraph/linkeddatahub/Application.java index abe64f03d..d10215d95 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/Application.java +++ b/src/main/java/com/atomgraph/linkeddatahub/Application.java @@ -16,6 +16,14 @@ */ package com.atomgraph.linkeddatahub; +import com.atomgraph.client.util.jena.PrefixGraphRepository; +import com.atomgraph.client.util.RDFSourceResolver; +import com.atomgraph.client.util.StylesheetResolver; +import com.atomgraph.linkeddatahub.writer.impl.SameSiteSourceResolver; +import com.atomgraph.linkeddatahub.server.util.OntologyRepository; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; import com.atomgraph.linkeddatahub.server.mapper.HttpHostConnectExceptionMapper; import com.atomgraph.linkeddatahub.server.mapper.InternalURLExceptionMapper; import com.atomgraph.linkeddatahub.server.mapper.MessagingExceptionMapper; @@ -26,10 +34,6 @@ import com.atomgraph.linkeddatahub.server.mapper.ForbiddenExceptionMapper; import com.atomgraph.linkeddatahub.server.mapper.auth.webid.WebIDCertificateExceptionMapper; import com.atomgraph.client.MediaTypes; -import com.atomgraph.client.locator.PrefixMapper; -import org.apache.jena.ontology.OntDocumentManager; -import org.apache.jena.util.FileManager; -import org.apache.jena.util.LocationMapper; import jakarta.annotation.PostConstruct; import jakarta.servlet.ServletConfig; import jakarta.ws.rs.core.Context; @@ -37,8 +41,6 @@ import org.apache.jena.riot.RDFFormat; import org.apache.jena.riot.RDFWriterRegistry; import com.atomgraph.client.mapper.ClientErrorExceptionMapper; -import com.atomgraph.client.util.DataManager; -import com.atomgraph.client.util.DataManagerImpl; import com.atomgraph.client.vocabulary.AC; import com.atomgraph.client.writer.function.UUID; import com.atomgraph.core.exception.ConfigurationException; @@ -49,7 +51,7 @@ import com.atomgraph.core.io.UpdateRequestProvider; import com.atomgraph.core.mapper.BadGatewayExceptionMapper; import com.atomgraph.core.provider.QueryParamProvider; -import com.atomgraph.linkeddatahub.writer.factory.DataManagerFactory; +import com.atomgraph.linkeddatahub.writer.factory.SourceResolverFactory; import com.atomgraph.server.vocabulary.LDT; import com.atomgraph.server.mapper.NotFoundExceptionMapper; import com.atomgraph.core.riot.RDFLanguages; @@ -67,7 +69,6 @@ import com.atomgraph.linkeddatahub.model.Service; import com.atomgraph.linkeddatahub.writer.factory.xslt.XsltExecutableSupplier; import com.atomgraph.linkeddatahub.writer.factory.XsltExecutableSupplierFactory; -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.JSONGRDDLFilter; @@ -135,7 +136,6 @@ import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import org.apache.jena.enhanced.BuiltinPersonalities; -import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.riot.RDFParserRegistry; import org.slf4j.Logger; import java.net.URI; @@ -164,7 +164,7 @@ import javax.net.ssl.TrustManagerFactory; import jakarta.servlet.ServletContext; import javax.xml.transform.Source; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.query.Dataset; import org.apache.jena.query.Query; import org.apache.jena.query.QueryExecution; @@ -174,7 +174,6 @@ import org.slf4j.LoggerFactory; import com.atomgraph.server.mapper.SHACLConstraintViolationExceptionMapper; import com.atomgraph.server.mapper.SPINConstraintViolationExceptionMapper; -import com.atomgraph.spinrdf.vocabulary.SP; import com.apicatalog.jsonld.JsonLdError; import com.apicatalog.jsonld.JsonLdOptions; import java.io.FileOutputStream; @@ -268,8 +267,9 @@ public class Application extends ResourceConfig private final ExecutorService importThreadPool; private final ServletConfig servletConfig; private final EventBus eventBus = new EventBus(); - private final DataManager dataManager; - private final Map endUserOntModelSpecs; + private final PrefixGraphRepository repository; + private final SameSiteSourceResolver resolver; + private final Map endUserRepositories; private final MediaTypes mediaTypes; private final Client client, externalClient, importClient, noCertClient; private final Query documentTypeQuery, documentOwnerQuery, aclQuery, ownerAclQuery, webIDQuery, agentQuery, userAccountQuery, ontologyQuery; // no relative URIs @@ -278,7 +278,6 @@ public class Application extends ResourceConfig private final Processor xsltProc = new Processor(false); private final XsltCompiler xsltComp; private final XsltExecutable xsltExec; - private final OntModelSpec ontModelSpec; private final boolean cacheStylesheet; private final boolean resolvingUncached; private final URI baseURI, uploadRoot; @@ -324,7 +323,7 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti servletConfig.getServletContext().getInitParameter(A.maxGetRequestSize.getURI()) != null ? Integer.valueOf(servletConfig.getServletContext().getInitParameter(A.maxGetRequestSize.getURI())) : null, servletConfig.getServletContext().getInitParameter(A.cacheModelLoads.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(A.cacheModelLoads.getURI())) : true, servletConfig.getServletContext().getInitParameter(A.preemptiveAuth.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(A.preemptiveAuth.getURI())) : false, - new PrefixMapper(servletConfig.getServletContext().getInitParameter(AC.prefixMapping.getURI()) != null ? servletConfig.getServletContext().getInitParameter(AC.prefixMapping.getURI()) : null), + servletConfig.getServletContext().getInitParameter(AC.prefixMapping.getURI()), servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI()) : null, com.atomgraph.client.Application.getSource(servletConfig.getServletContext(), servletConfig.getServletContext().getInitParameter(AC.stylesheet.getURI()) != null ? servletConfig.getServletContext().getInitParameter(AC.stylesheet.getURI()) : null), servletConfig.getServletContext().getInitParameter(AC.cacheStylesheet.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(AC.cacheStylesheet.getURI())) : false, @@ -435,7 +434,7 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti */ public Application(final ServletConfig servletConfig, final MediaTypes mediaTypes, final Integer maxGetRequestSize, final boolean cacheModelLoads, final boolean preemptiveAuth, - final LocationMapper locationMapper, final String contextDatasetURIString, + final String prefixMappingConfig, final String contextDatasetURIString, final Source stylesheet, final boolean cacheStylesheet, final boolean resolvingUncached, final String clientKeyStoreURIString, final String clientKeyStorePassword, final String secretaryCertAlias, @@ -682,12 +681,8 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType } // register plain RDF/XML writer as default - RDFWriterRegistry.register(Lang.RDFXML, RDFFormat.RDFXML_PLAIN); + RDFWriterRegistry.register(Lang.RDFXML, RDFFormat.RDFXML_PLAIN); - // initialize mapping for locally stored vocabularies - LocationMapper.setGlobalLocationMapper(locationMapper); - if (log.isTraceEnabled()) log.trace("LocationMapper.get(): {}", locationMapper); - try { this.contextDataset = getDataset(servletConfig.getServletContext(), contextDatasetURI); @@ -747,7 +742,6 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType throw new IllegalStateException(new CertificateException("Secretary certificate with alias " + secretaryCertAlias + " not a valid WebID sertificate (SNA URI is missing)")); } - SP.init(BuiltinPersonalities.model); BuiltinPersonalities.model.add(Authorization.class, AuthorizationImpl.factory); BuiltinPersonalities.model.add(Agent.class, AgentImpl.factory); BuiltinPersonalities.model.add(UserAccount.class, UserAccountImpl.factory); @@ -792,14 +786,16 @@ else if (app.hasProperty(RDF.type, LAPP.EndUserApplication)) serviceIt.close(); } - // TO-DO: config property for cacheModelLoads - endUserOntModelSpecs = new HashMap<>(); - dataManager = new DataManagerImpl(locationMapper, new HashMap<>(), GraphStoreClient.create(client, mediaTypes), cacheModelLoads, preemptiveAuth, resolvingUncached); - ontModelSpec = OntModelSpec.OWL_MEM_RDFS_INF; - ontModelSpec.setImportModelGetter(dataManager); - OntDocumentManager.getInstance().setFileManager((FileManager)dataManager); - OntDocumentManager.getInstance().setCacheModels(true); // need to re-set after changing FileManager - ontModelSpec.setDocumentManager(OntDocumentManager.getInstance()); + endUserRepositories = new HashMap<>(); + // global graph repository: bundled vocabularies/ontologies mapped from the prefix-mapping config + repository = new PrefixGraphRepository(GraphStoreClient.create(client, mediaTypes)); + if (prefixMappingConfig != null) + { + Model prefixMappingModel = ModelFactory.createDefaultModel(); + RDFParser.create().source(prefixMappingConfig).streamManager(repository.getStreamManager()).build().parse(prefixMappingModel); + repository.processConfig(prefixMappingModel); + } + resolver = new SameSiteSourceResolver(repository, GraphStoreClient.create(client, mediaTypes), resolvingUncached, baseURI); if (mailUser != null && mailPassword != null) // enable SMTP authentication { @@ -836,19 +832,15 @@ protected PasswordAuthentication getPasswordAuthentication() xsltProc.registerExtensionFunction(new com.atomgraph.linkeddatahub.writer.function.URLDecode()); xsltProc.registerExtensionFunction(new com.atomgraph.linkeddatahub.writer.function.SendHTTPRequest(xsltProc, client)); - Model mappingModel = locationMapper.toModel(); - ResIterator prefixedMappings = mappingModel.listResourcesWithProperty(LocationMappingVocab.prefix); try { - while (prefixedMappings.hasNext()) + for (String prefix : getRepository().getPrefixMappings().keySet()) { - Resource prefixMapping = prefixedMappings.next(); - String prefix = prefixMapping.getRequiredProperty(LocationMappingVocab.prefix).getString(); // register mapped RDF documents in the XSLT processor so that document() returns them cached, throughout multiple transformations - TreeInfo doc = xsltProc.getUnderlyingConfiguration().buildDocumentTree(dataManager.resolve("", prefix)); + TreeInfo doc = xsltProc.getUnderlyingConfiguration().buildDocumentTree(getResolver().resolve("", prefix)); xsltProc.getUnderlyingConfiguration().getGlobalDocumentPool().add(doc, prefix); } - + // register HTTPS URL of translations.rdf so it doesn't have to be requested repeatedly try (InputStream translations = servletConfig.getServletContext().getResourceAsStream(XSLTWriterBase.TRANSLATIONS_PATH)) { @@ -861,14 +853,10 @@ protected PasswordAuthentication getPasswordAuthentication() if (log.isErrorEnabled()) log.error("Error reading mapped RDF document: {}", ex); throw new IllegalStateException(ex); } - finally - { - prefixedMappings.close(); - } xsltComp = xsltProc.newXsltCompiler(); xsltComp.setParameter(new QName("ldh", LDH.base.getNameSpace(), LDH.base.getLocalName()), new XdmAtomicValue(baseURI)); - xsltComp.setURIResolver(new XsltResolver(LocationMapper.get(), new HashMap<>(), GraphStoreClient.create(client, mediaTypes), false, false, true)); // default Xerces parser does not support HTTPS + xsltComp.setURIResolver(new StylesheetResolver(getRepository(), GraphStoreClient.create(client, mediaTypes))); // resolves xsl:import to raw stylesheet sources xsltExec = xsltComp.compile(stylesheet); } catch (FileNotFoundException ex) @@ -935,8 +923,8 @@ public void init() register(new QueryProvider()); register(new QueryParamProvider()); register(new UpdateRequestProvider()); - register(new ModelXSLTWriter(getXsltExecutable(), getOntModelSpec(), getDataManager(), getMessageDigest())); // writes (X)HTML responses - register(new ResultSetXSLTWriter(getXsltExecutable(), getOntModelSpec(), getDataManager(), getMessageDigest())); // writes (X)HTML responses + register(new ModelXSLTWriter(getXsltExecutable(), getResolver(), getMessageDigest())); // writes (X)HTML responses + register(new ResultSetXSLTWriter(getXsltExecutable(), getResolver(), getMessageDigest())); // writes (X)HTML responses final com.atomgraph.linkeddatahub.Application system = this; register(new AbstractBinder() @@ -1014,7 +1002,7 @@ protected void configure() @Override protected void configure() { - bindFactory(OntologyFactory.class).to(new TypeLiteral>() {}). + bindFactory(OntologyFactory.class).to(new TypeLiteral>() {}). in(RequestScoped.class); } }); @@ -1023,15 +1011,7 @@ protected void configure() @Override protected void configure() { - bindFactory(new com.atomgraph.core.factory.DataManagerFactory(getDataManager())).to(com.atomgraph.core.util.jena.DataManager.class); - } - }); - register(new AbstractBinder() - { - @Override - protected void configure() - { - bindFactory(DataManagerFactory.class).to(com.atomgraph.client.util.DataManager.class). + bindFactory(SourceResolverFactory.class).to(com.atomgraph.client.util.RDFSourceResolver.class). in(RequestScoped.class); } }); @@ -1756,43 +1736,54 @@ public EventBus getEventBus() } /** - * Gets Jena's DataManager implementation. - * - * @return data manager instance + * Returns the global graph repository (bundled vocabularies/ontologies + URI mapping + cache). + * + * @return graph repository + */ + public PrefixGraphRepository getRepository() + { + return repository; + } + + /** + * Returns the global XSLT source resolver. + * + * @return source resolver */ - public DataManager getDataManager() + public SameSiteSourceResolver getResolver() { - return dataManager; + return resolver; } - + /** - * Returns a map of application URIs to ontology specifications. - * - * @return URI to ontology specification map + * Returns a map of application URIs to ontology repositories. + * + * @return URI to ontology repository map */ - protected Map getEndUserOntModelSpecs() + protected Map getEndUserRepositories() { - return endUserOntModelSpecs; + return endUserRepositories; } /** - * Returns ontology specification for the specified end-user application. - * + * Returns the SPARQL-first ontology repository for the specified end-user application. + * * @param app end-user application resource - * @return ontology specification + * @return ontology repository */ - public OntModelSpec getOntModelSpec(EndUserApplication app) + public OntologyRepository getRepository(EndUserApplication app) { - if (!getEndUserOntModelSpecs().containsKey(app.getURI())) + if (!getEndUserRepositories().containsKey(app.getURI())) { - OntModelSpec appOntModelSpec = new OntModelSpec(OntModelSpec.OWL_MEM_RDFS_INF); - appOntModelSpec.setDocumentManager(new OntDocumentManager()); - appOntModelSpec.getDocumentManager().setFileManager(new DataManagerImpl(LocationMapper.get(), new HashMap<>(), GraphStoreClient.create(getClient(), getMediaTypes()), true, isPreemptiveAuth(), isResolvingUncached())); - - getEndUserOntModelSpecs().put(app.getURI(), appOntModelSpec); + OntologyRepository appRepository = new OntologyRepository(app, this, GraphStoreClient.create(getClient(), getMediaTypes()), getOntologyQuery()); + // seed bundled vocabulary/ontology mappings from the global repository + getRepository().getLocationMappings().forEach(appRepository::addLocationMapping); + getRepository().getPrefixMappings().forEach(appRepository::addPrefixMapping); + + getEndUserRepositories().put(app.getURI(), appRepository); } - - return getEndUserOntModelSpecs().get(app.getURI()); + + return getEndUserRepositories().get(app.getURI()); } /** @@ -2002,16 +1993,6 @@ public boolean isPreemptiveAuth() return preemptiveAuth; } - /** - * The default specification of ontology models. - * - * @return spec object - */ - public OntModelSpec getOntModelSpec() - { - return ontModelSpec; - } - /** * Returns Saxon's XSLT compiler. * diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java b/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java index 34b68d81f..27e2124ff 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/Generate.java @@ -42,7 +42,7 @@ import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.query.ParameterizedSparqlString; import org.apache.jena.query.Query; import org.apache.jena.query.QueryFactory; @@ -69,7 +69,7 @@ public class Generate private final UriInfo uriInfo; private final MediaTypes mediaTypes; private final Application application; - private final Ontology ontology; + private final OntModel ontology; private final Optional agentContext; private final com.atomgraph.linkeddatahub.Application system; private final ResourceContext resourceContext; @@ -88,7 +88,7 @@ public class Generate */ @Inject public Generate(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes, - com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional agentContext, + com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional agentContext, com.atomgraph.linkeddatahub.Application system, @Context ResourceContext resourceContext) { if (ontology.isEmpty()) throw new InternalServerErrorException("Ontology is not specified"); @@ -134,7 +134,7 @@ public Response post(Model model) if (queryRes == null) throw new BadRequestException("Container query string (spin:query) not provided"); // Lookup query in ontology - Resource queryResource = getOntology().getOntModel().getResource(queryRes.getURI()); + Resource queryResource = getOntology().getResource(queryRes.getURI()); if (queryResource == null || !queryResource.hasProperty(SP.text)) throw new BadRequestException("Query resource not found in ontology: " + queryRes.getURI()); @@ -265,7 +265,7 @@ public Application getApplication() * * @return the ontology */ - public Ontology getOntology() + public OntModel getOntology() { return ontology; } diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java b/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java index 4ccefa319..c055f957d 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/Namespace.java @@ -31,7 +31,7 @@ import com.atomgraph.core.model.impl.dataset.ServiceImpl; import com.atomgraph.linkeddatahub.apps.model.Application; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; -import com.atomgraph.linkeddatahub.server.util.OntologyModelGetter; +import com.atomgraph.linkeddatahub.server.util.OntologyRepository; import java.net.URI; import java.util.List; import java.util.Optional; @@ -47,7 +47,7 @@ import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.UriInfo; import org.apache.jena.irix.IRIx; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.query.DatasetFactory; import org.apache.jena.query.Query; import org.apache.jena.query.QueryFactory; @@ -70,7 +70,7 @@ public class Namespace extends com.atomgraph.core.model.impl.SPARQLEndpointImpl private final URI uri; private final UriInfo uriInfo; private final Application application; - private final Ontology ontology; + private final OntModel ontology; private final com.atomgraph.linkeddatahub.Application system; /** @@ -86,10 +86,10 @@ public class Namespace extends com.atomgraph.core.model.impl.SPARQLEndpointImpl */ @Inject public Namespace(@Context Request request, @Context UriInfo uriInfo, - Application application, Optional ontology, MediaTypes mediaTypes, + Application application, Optional ontology, MediaTypes mediaTypes, @Context SecurityContext securityContext, com.atomgraph.linkeddatahub.Application system) { - super(request, new ServiceImpl(DatasetFactory.create(ontology.get().getOntModel()), mediaTypes), mediaTypes); + super(request, new ServiceImpl(DatasetFactory.create(ontology.get()), mediaTypes), mediaTypes); this.uri = uriInfo.getAbsolutePath(); this.uriInfo = uriInfo; this.application = application; @@ -128,7 +128,7 @@ public Response get(@QueryParam(QUERY) Query query, Model instances = ModelFactory.createDefaultModel(); forClasses.stream(). - map(forClass -> Optional.ofNullable(getOntology().getOntModel().getOntClass(checkURI(forClass).toString()))). + map(forClass -> Optional.ofNullable(getOntology().getOntClass(checkURI(forClass).toString()))). flatMap(Optional::stream). forEach(forClass -> new Constructor().construct(forClass, instances, getApplication().getBase().getURI())); @@ -141,8 +141,8 @@ public Response get(@QueryParam(QUERY) Query query, String ontologyURI = getApplication().getOntology().getURI(); if (log.isDebugEnabled()) log.debug("Returning namespace ontology from OntDocumentManager: {}", ontologyURI); // not returning the injected in-memory ontology because it has inferences applied to it - OntologyModelGetter modelGetter = new OntologyModelGetter(getApplication().as(EndUserApplication.class), getSystem(), getSystem().getOntModelSpec(), getSystem().getOntologyQuery()); - return getResponseBuilder(modelGetter.getModel(ontologyURI)).build(); + OntologyRepository repository = new OntologyRepository(getApplication().as(EndUserApplication.class), getSystem(), com.atomgraph.linkeddatahub.client.GraphStoreClient.create(getSystem().getClient(), getSystem().getMediaTypes()), getSystem().getOntologyQuery()); + return getResponseBuilder(org.apache.jena.rdf.model.ModelFactory.createModelForGraph(repository.get(ontologyURI))).build(); } else throw new BadRequestException("SPARQL query string not provided"); } @@ -223,7 +223,7 @@ public Application getApplication() * * @return application ontology */ - public Ontology getOntology() + public OntModel getOntology() { return ontology; } diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/ClearOntology.java b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/ClearOntology.java index 202f72429..75ab778a9 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/ClearOntology.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/ClearOntology.java @@ -18,9 +18,8 @@ import com.atomgraph.linkeddatahub.apps.model.AdminApplication; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; -import static com.atomgraph.linkeddatahub.server.filter.request.OntologyFilter.addDocumentModel; import com.atomgraph.linkeddatahub.server.filter.response.CacheInvalidationFilter; -import com.atomgraph.linkeddatahub.server.util.OntologyModelGetter; +import com.atomgraph.linkeddatahub.server.util.OntologyRepository; import java.net.URI; import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; @@ -31,8 +30,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; +import com.atomgraph.linkeddatahub.server.filter.request.OntologyFilter; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; @@ -82,11 +80,11 @@ public Response post(@FormParam("uri") String ontologyURI, @HeaderParam("Referer if (ontologyURI == null) throw new BadRequestException("Ontology URI not specified"); EndUserApplication endUserApp = getApplication().as(AdminApplication.class).getEndUserApplication(); // we're assuming the current app is admin - OntModelSpec ontModelSpec = new OntModelSpec(getSystem().getOntModelSpec(endUserApp)); - if (ontModelSpec.getDocumentManager().getFileManager().hasCachedModel(ontologyURI)) + OntologyRepository repository = getSystem().getRepository(endUserApp); + if (repository.isCached(ontologyURI)) { if (log.isDebugEnabled()) log.debug("Clearing ontology with URI '{}' from memory", ontologyURI); - ontModelSpec.getDocumentManager().getFileManager().removeCacheModel(ontologyURI); + repository.remove(ontologyURI); URI ontologyDocURI = UriBuilder.fromUri(ontologyURI).fragment(null).build(); // skip fragment from the ontology URI to get its graph URI // frontend proxy still uses URL-pattern BAN for direct document GETs (until Stage 3 brings xkey tagging to varnish-frontend). @@ -116,19 +114,7 @@ public Response post(@FormParam("uri") String ontologyURI, @HeaderParam("Referer } // !!! we need to reload the ontology model before returning a response, to make sure the next request already gets the new version !!! - // same logic as in OntologyFilter. TO-DO: encapsulate? - OntologyModelGetter modelGetter = new OntologyModelGetter(endUserApp, getSystem(), ontModelSpec, getSystem().getOntologyQuery()); - ontModelSpec.setImportModelGetter(modelGetter); - if (log.isDebugEnabled()) log.debug("Started loading ontology with URI '{}' from the admin dataset", ontologyURI); - Model baseModel = modelGetter.getModel(ontologyURI); - OntModel ontModel = ModelFactory.createOntologyModel(ontModelSpec, baseModel); - // materialize OntModel inferences to avoid invoking rules engine on every request - OntModel materializedModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); // no inference - materializedModel.add(ontModel); - ontModel.getDocumentManager().addModel(ontologyURI, materializedModel, true); // make immutable and add as OntModel so that imports do not need to be reloaded during retrieval - // make sure to cache imported models not only by ontology URI but also by document URI - ontModel.listImportedOntologyURIs(true).forEach((String importURI) -> addDocumentModel(ontModel.getDocumentManager(), importURI)); - if (log.isDebugEnabled()) log.debug("Finished loading ontology with URI '{}' from the admin dataset", ontologyURI); + OntologyFilter.loadOntology(repository, ontologyURI); } if (referer != null) return Response.seeOther(referer).build(); diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java index d71770a2c..bc8467d0d 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/SignUp.java @@ -72,7 +72,7 @@ import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.ext.Providers; import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDhexBinary; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.query.ParameterizedSparqlString; import org.apache.jena.query.Query; import org.apache.jena.query.ResultSet; @@ -144,7 +144,7 @@ public class SignUp extends DocumentHierarchyGraphStoreImpl // TO-DO: move to AuthenticationExceptionMapper and handle as state instead of URI resource? @Inject public SignUp(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes, - com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service, + com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service, @Context SecurityContext securityContext, Optional agentContext, @Context Providers providers, com.atomgraph.linkeddatahub.Application system, @Context ServletConfig servletConfig) { diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/InstallPackage.java b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/InstallPackage.java index 9e760cd0b..e178f6dc6 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/InstallPackage.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/InstallPackage.java @@ -17,7 +17,6 @@ package com.atomgraph.linkeddatahub.resource.admin.pkg; import static com.atomgraph.client.MediaType.TEXT_XSL; -import com.atomgraph.client.util.DataManager; import com.atomgraph.linkeddatahub.apps.model.AdminApplication; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; import com.atomgraph.linkeddatahub.client.GraphStoreClient; @@ -60,7 +59,6 @@ import org.apache.jena.ontology.ConversionException; import org.apache.jena.update.UpdateFactory; import org.apache.jena.update.UpdateRequest; -import org.apache.jena.util.FileManager; import com.atomgraph.linkeddatahub.vocabulary.DH; import com.atomgraph.linkeddatahub.vocabulary.FOAF; import com.atomgraph.linkeddatahub.vocabulary.SIOC; @@ -88,7 +86,6 @@ public class InstallPackage private final com.atomgraph.linkeddatahub.apps.model.Application application; private final com.atomgraph.linkeddatahub.Application system; - private final DataManager dataManager; private final Optional agentContext; @Context ServletContext servletContext; @@ -105,12 +102,10 @@ public class InstallPackage @Inject public InstallPackage(com.atomgraph.linkeddatahub.apps.model.Application application, com.atomgraph.linkeddatahub.Application system, - DataManager dataManager, Optional agentContext) { this.application = application; this.system = system; - this.dataManager = dataManager; this.agentContext = agentContext; } @@ -242,12 +237,12 @@ private com.atomgraph.linkeddatahub.apps.model.Package getPackage(String package final Model model; // check if we have the model in the cache first and if yes, return it from there instead making an HTTP request - if (((FileManager)getDataManager()).hasCachedModel(packageURI) || - (getDataManager().isResolvingMapped() && getDataManager().isMapped(packageURI))) // read mapped URIs (such as system ontologies) from a file + if (getSystem().getRepository().isCached(packageURI) || + (getSystem().getRepository().isMapped(packageURI))) // read mapped URIs (such as system ontologies) from a file { - if (log.isDebugEnabled()) log.debug("hasCachedModel({}): {}", packageURI, ((FileManager)getDataManager()).hasCachedModel(packageURI)); - if (log.isDebugEnabled()) log.debug("isMapped({}): {}", packageURI, getDataManager().isMapped(packageURI)); - model = getDataManager().loadModel(packageURI); + if (log.isDebugEnabled()) log.debug("hasCachedModel({}): {}", packageURI, getSystem().getRepository().isCached(packageURI)); + if (log.isDebugEnabled()) log.debug("isMapped({}): {}", packageURI, getSystem().getRepository().isMapped(packageURI)); + model = ModelFactory.createModelForGraph(getSystem().getRepository().get(packageURI)); } else { @@ -284,12 +279,12 @@ private Model downloadOntology(String uri) if (log.isDebugEnabled()) log.debug("Downloading ontology from: {}", uri); // check if we have the model in the cache first and if yes, return it from there instead making an HTTP request - if (((FileManager)getDataManager()).hasCachedModel(uri) || - (getDataManager().isResolvingMapped() && getDataManager().isMapped(uri))) // read mapped URIs (such as system ontologies) from a file + if (getSystem().getRepository().isCached(uri) || + (getSystem().getRepository().isMapped(uri))) // read mapped URIs (such as system ontologies) from a file { - if (log.isDebugEnabled()) log.debug("hasCachedModel({}): {}", uri, ((FileManager)getDataManager()).hasCachedModel(uri)); - if (log.isDebugEnabled()) log.debug("isMapped({}): {}", uri, getDataManager().isMapped(uri)); - return getDataManager().loadModel(uri); + if (log.isDebugEnabled()) log.debug("hasCachedModel({}): {}", uri, getSystem().getRepository().isCached(uri)); + if (log.isDebugEnabled()) log.debug("isMapped({}): {}", uri, getSystem().getRepository().isMapped(uri)); + return ModelFactory.createModelForGraph(getSystem().getRepository().get(uri)); } else { @@ -522,15 +517,6 @@ public ServletContext getServletContext() return servletContext; } - /** - * Returns RDF data manager. - * - * @return RDF data manager - */ - public DataManager getDataManager() - { - return dataManager; - } /** * Returns JAX-RS resource context. diff --git a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/UninstallPackage.java b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/UninstallPackage.java index fb0cb5f3c..28d37ca9f 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/UninstallPackage.java +++ b/src/main/java/com/atomgraph/linkeddatahub/resource/admin/pkg/UninstallPackage.java @@ -16,7 +16,6 @@ */ package com.atomgraph.linkeddatahub.resource.admin.pkg; -import com.atomgraph.client.util.DataManager; import com.atomgraph.linkeddatahub.apps.model.AdminApplication; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; import com.atomgraph.linkeddatahub.client.GraphStoreClient; @@ -39,10 +38,10 @@ import jakarta.ws.rs.core.UriBuilder; import org.apache.commons.codec.binary.Hex; import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; import org.apache.jena.update.UpdateFactory; import org.apache.jena.update.UpdateRequest; -import org.apache.jena.util.FileManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; @@ -73,7 +72,6 @@ public class UninstallPackage private final com.atomgraph.linkeddatahub.apps.model.Application application; private final com.atomgraph.linkeddatahub.Application system; - private final DataManager dataManager; private final Optional agentContext; @Context ServletContext servletContext; @@ -90,12 +88,10 @@ public class UninstallPackage @Inject public UninstallPackage(com.atomgraph.linkeddatahub.apps.model.Application application, com.atomgraph.linkeddatahub.Application system, - DataManager dataManager, Optional agentContext) { this.application = application; this.system = system; - this.dataManager = dataManager; this.agentContext = agentContext; } @@ -317,12 +313,12 @@ private com.atomgraph.linkeddatahub.apps.model.Package getPackage(String package final Model model; // check if we have the model in the cache first and if yes, return it from there instead making an HTTP request - if (((FileManager)getDataManager()).hasCachedModel(packageURI) || - (getDataManager().isResolvingMapped() && getDataManager().isMapped(packageURI))) // read mapped URIs (such as system ontologies) from a file + if (getSystem().getRepository().isCached(packageURI) || + (getSystem().getRepository().isMapped(packageURI))) // read mapped URIs (such as system ontologies) from a file { - if (log.isDebugEnabled()) log.debug("hasCachedModel({}): {}", packageURI, ((FileManager)getDataManager()).hasCachedModel(packageURI)); - if (log.isDebugEnabled()) log.debug("isMapped({}): {}", packageURI, getDataManager().isMapped(packageURI)); - model = getDataManager().loadModel(packageURI); + if (log.isDebugEnabled()) log.debug("hasCachedModel({}): {}", packageURI, getSystem().getRepository().isCached(packageURI)); + if (log.isDebugEnabled()) log.debug("isMapped({}): {}", packageURI, getSystem().getRepository().isMapped(packageURI)); + model = ModelFactory.createModelForGraph(getSystem().getRepository().get(packageURI)); } else { @@ -350,15 +346,6 @@ public com.atomgraph.linkeddatahub.Application getSystem() return system; } - /** - * Returns RDF data manager. - * - * @return RDF data manager - */ - public DataManager getDataManager() - { - return dataManager; - } /** * Returns JAX-RS resource context. diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/event/AuthorizationCreated.java b/src/main/java/com/atomgraph/linkeddatahub/server/event/AuthorizationCreated.java index 7353e0297..3b02124d2 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/event/AuthorizationCreated.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/event/AuthorizationCreated.java @@ -16,7 +16,6 @@ */ package com.atomgraph.linkeddatahub.server.event; -import com.atomgraph.core.util.jena.DataManager; import com.atomgraph.linkeddatahub.apps.model.Application; import com.atomgraph.linkeddatahub.client.GraphStoreClient; import org.apache.jena.rdf.model.Resource; diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/factory/OntologyFactory.java b/src/main/java/com/atomgraph/linkeddatahub/server/factory/OntologyFactory.java index 39fe494b6..8fc56c6a2 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/factory/OntologyFactory.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/factory/OntologyFactory.java @@ -20,7 +20,7 @@ import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.vocabulary.OWL; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.api.ServiceLocator; @@ -31,19 +31,19 @@ * @author Martynas Jusevičius {@literal } */ @Provider -public class OntologyFactory implements Factory> +public class OntologyFactory implements Factory> { @Context private ServiceLocator serviceLocator; @Override - public Optional provide() + public Optional provide() { return getOntology(); } @Override - public void dispose(Optional t) + public void dispose(Optional t) { } @@ -52,9 +52,9 @@ public void dispose(Optional t) * * @return ontology */ - public Optional getOntology() + public Optional getOntology() { - return (Optional)getContainerRequestContext().getProperty(OWL.Ontology.getURI()); + return (Optional)getContainerRequestContext().getProperty(OWL.Ontology.getURI()); } /** diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilter.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilter.java index 03ef83662..5312cc2e5 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilter.java @@ -18,27 +18,28 @@ import com.atomgraph.linkeddatahub.apps.model.Application; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; +import com.atomgraph.client.util.jena.PrefixGraphRepository; import com.atomgraph.linkeddatahub.vocabulary.LAPP; import com.atomgraph.server.exception.OntologyException; -import com.atomgraph.linkeddatahub.server.util.OntologyModelGetter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import jakarta.annotation.Priority; import jakarta.inject.Inject; -import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; -import org.apache.jena.ontology.OntDocumentManager; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.util.FileManager; import org.apache.jena.vocabulary.OWL; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,7 +101,7 @@ public void filter(ContainerRequestContext crc) throws IOException * @param crc request context * @return optional ontology */ - public Optional getOntology(ContainerRequestContext crc) + public Optional getOntology(ContainerRequestContext crc) { Optional appOpt = getApplication(crc); @@ -115,99 +116,104 @@ public Optional getOntology(ContainerRequestContext crc) return Optional.empty(); } } - + /** * Gets ontology of the specified application. - * + * * @param app application resource - * @return ontology resource + * @return ontology model */ - public Ontology getOntology(Application app) + public OntModel getOntology(Application app) { if (app.getOntology() == null) return null; return getOntology(app, app.getOntology().getURI()); } - + /** - * Loads ontology using the specified ontology URI. - * + * Loads the ontology model for the specified ontology URI, building its owl:imports closure with + * RDFS inference and materializing the inferences into the repository cache. + * * @param app application resource * @param uri ontology URI - * @return ontology resource + * @return ontology model */ - public Ontology getOntology(Application app, String uri) + public OntModel getOntology(Application app, String uri) { - if (app == null) throw new IllegalArgumentException("Application string cannot be null"); - if (uri == null) throw new IllegalArgumentException("Ontology URI string cannot be null"); + if (app == null) throw new IllegalArgumentException("Application cannot be null"); + if (uri == null) throw new IllegalArgumentException("Ontology URI cannot be null"); - final OntModelSpec ontModelSpec; - if (app.canAs(EndUserApplication.class)) - { - ontModelSpec = new OntModelSpec(getSystem().getOntModelSpec(app.as(EndUserApplication.class))); - // only create InfModel if ontology is not already cached - if (!ontModelSpec.getDocumentManager().getFileManager().hasCachedModel(uri)) - { - OntologyModelGetter modelGetter = new OntologyModelGetter(app.as(EndUserApplication.class), getSystem(), ontModelSpec, getSystem().getOntologyQuery()); - ontModelSpec.setImportModelGetter(modelGetter); - if (log.isDebugEnabled()) log.debug("Started loading ontology with URI '{}' from the admin dataset", uri); - Model baseModel = modelGetter.getModel(uri); - OntModel ontModel = ModelFactory.createOntologyModel(ontModelSpec, baseModel); - // materialize OntModel inferences to avoid invoking rules engine on every request - OntModel materializedModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); // no inference - materializedModel.add(ontModel); - ontModel.getDocumentManager().addModel(uri, materializedModel, true); // make immutable and add as OntModel so that imports do not need to be reloaded during retrieval - // make sure to cache imported models not only by ontology URI but also by document URI - ontModel.listImportedOntologyURIs(true).forEach((String importURI) -> addDocumentModel(ontModel.getDocumentManager(), importURI)); - if (log.isDebugEnabled()) log.debug("Finished loading ontology with URI '{}' from the admin dataset", uri); - } - } - else + final PrefixGraphRepository repository = app.canAs(EndUserApplication.class) ? + getSystem().getRepository(app.as(EndUserApplication.class)) : getSystem().getRepository(); + + // only build the materialized model if the ontology is not already cached + if (!repository.isCached(uri)) loadOntology(repository, uri); + + return OntModelFactory.createModel(repository.get(uri), OntSpecification.OWL2_FULL_MEM); + } + + /** + * Builds and caches the materialized ontology model. Assembles the owl:imports closure into a single + * graph (so ontapi never manages a union-graph hierarchy over the shared repository), applies RDFS + * inference over the flattened closure, and materializes the inferences into the repository cache so + * the rules engine is not invoked on every request. + * + * @param repository graph repository + * @param uri ontology URI + */ + public static void loadOntology(PrefixGraphRepository repository, String uri) + { + if (log.isDebugEnabled()) log.debug("Started loading ontology with URI '{}'", uri); + Model union = ModelFactory.createDefaultModel(); + Set closure = new HashSet<>(); + loadClosure(repository, uri, union, closure); + OntModel inferred = OntModelFactory.createModel(union.getGraph(), OntSpecification.OWL2_FULL_MEM_RDFS_INF); + OntModel materialized = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); + materialized.add(inferred); + // promote rdfs:Class to owl:Class so OWL2 profiles recognise third-party vocab terms (e.g. sp:Describe in sp.ttl) + inferred.listSubjectsWithProperty(RDF.type, RDFS.Class).forEach(r -> materialized.add(r, RDF.type, OWL.Class)); + repository.put(uri, materialized.getGraph()); + // cache imported graphs under their fragment-stripped document URIs too + closure.stream().filter(closureURI -> !closureURI.equals(uri)).forEach(importURI -> addDocumentModel(repository, importURI)); + if (log.isDebugEnabled()) log.debug("Finished loading ontology with URI '{}'", uri); + } + + /** + * Recursively loads the transitive owl:imports closure of an ontology into a single union model, + * fetching each graph via the repository (SPARQL-first / bundled mappings). + * + * @param repository graph repository + * @param uri ontology URI + * @param union accumulator model + * @param seen accumulator of visited URIs (prevents cycles) + */ + public static void loadClosure(PrefixGraphRepository repository, String uri, Model union, Set seen) + { + if (!seen.add(uri)) return; + Model model = ModelFactory.createModelForGraph(repository.get(uri)); + union.add(model); + model.listObjectsOfProperty(OWL.imports).toList().forEach(imp -> { - ontModelSpec = new OntModelSpec(getSystem().getOntModelSpec()); - FileManager fileManager = ontModelSpec.getDocumentManager().getFileManager(); - if (!fileManager.hasCachedModel(uri)) - { - try - { - URI ontologyURI = URI.create(uri); - // remove fragment and normalize - URI ontDocURI = new URI(ontologyURI.getScheme(), ontologyURI.getSchemeSpecificPart(), null).normalize(); - Model baseModel = fileManager.loadModel(ontDocURI.toString()); - OntModel ontModel = ModelFactory.createOntologyModel(ontModelSpec, baseModel); - ontModel.getDocumentManager().addModel(uri, ontModel, true); - } - catch (URISyntaxException ex) - { - if (log.isErrorEnabled()) log.error("Ontology URI syntax error: {}", ex.getInput()); - throw new InternalServerErrorException(ex); - } - } - } - return ontModelSpec.getDocumentManager().getOntology(uri, ontModelSpec).getOntology(uri); // reloads the imports using ModelGetter. TO-DO: optimize? + if (imp.isURIResource()) loadClosure(repository, imp.asResource().getURI(), union, seen); + }); } /** - * Extracts document URI from ontology import URI and uses it as a secondary cache key. - * - * @param odm document manager + * Caches an imported graph under its fragment-stripped document URI as a secondary cache key. + * + * @param repository graph repository * @param importURI ontology URI */ - public static void addDocumentModel(OntDocumentManager odm, String importURI) + public static void addDocumentModel(PrefixGraphRepository repository, String importURI) { try { URI ontologyURI = URI.create(importURI); // remove fragment and normalize URI docURI = new URI(ontologyURI.getScheme(), ontologyURI.getSchemeSpecificPart(), null).normalize(); - String mappedURI = odm.getFileManager().mapURI(docURI.toString()); - // only cache import document URI if it's not already cached or mapped - if (!odm.getFileManager().hasCachedModel(docURI.toString()) && mappedURI.equals(docURI.toString())) - { - Model importModel = odm.getModel(importURI); - if (importModel == null) throw new IllegalArgumentException("Import model is not cached"); - odm.addModel(docURI.toString(), importModel, true); - } + // only cache the document URI if it is not already cached or mapped to a different location + if (!repository.isCached(docURI.toString()) && repository.resolve(docURI.toString()).equals(docURI.toString())) + repository.put(docURI.toString(), repository.get(importURI)); } catch (URISyntaxException ex) { diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java index 4eeb6599e..9166b28ea 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java @@ -22,7 +22,7 @@ import com.atomgraph.core.exception.BadGatewayException; import com.atomgraph.core.util.ModelUtils; import com.atomgraph.linkeddatahub.apps.model.Dataset; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import com.atomgraph.linkeddatahub.client.GraphStoreClient; import com.atomgraph.linkeddatahub.client.filter.auth.IDTokenDelegationFilter; import com.atomgraph.linkeddatahub.client.filter.auth.WebIDDelegationFilter; @@ -133,7 +133,7 @@ public class ProxyRequestFilter implements ContainerRequestFilter "Age"); @Inject com.atomgraph.linkeddatahub.Application system; - @Inject jakarta.inject.Provider> ontology; + @Inject jakarta.inject.Provider> ontology; @Inject MediaTypes mediaTypes; @Context Request request; @@ -178,10 +178,10 @@ public void filter(ContainerRequestContext requestContext) throws IOException || "HEAD".equalsIgnoreCase(requestContext.getMethod()); // serve mapped URIs (e.g. system ontologies) directly from the DataManager cache - if (isSafeMethod && getSystem().getDataManager().isMapped(targetURI.toString())) + if (isSafeMethod && getSystem().getRepository().isMapped(targetURI.toString())) { if (log.isDebugEnabled()) log.debug("Serving mapped URI from DataManager cache: {}", targetURI); - Model model = getSystem().getDataManager().loadModel(targetURI.toString()); + Model model = org.apache.jena.rdf.model.ModelFactory.createModelForGraph(getSystem().getRepository().get(targetURI.toString())); requestContext.abortWith(getResponse(model, Response.Status.OK)); return; } @@ -195,7 +195,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException ParameterizedSparqlString pss = new ParameterizedSparqlString( "DESCRIBE ?doc ?term WHERE { ?term ?p ?o FILTER(STRSTARTS(STR(?term), CONCAT(STR(?doc), \"#\"))) }"); pss.setIri("doc", targetURI.toString()); - try (QueryExecution qe = QueryExecution.create(pss.asQuery(), getOntology().get().getOntModel())) + try (QueryExecution qe = QueryExecution.create(pss.asQuery(), getOntology().get())) { Model description = qe.execDescribe(); if (!description.isEmpty()) @@ -486,7 +486,7 @@ public com.atomgraph.linkeddatahub.Application getSystem() * * @return optional ontology */ - public Optional getOntology() + public Optional getOntology() { return ontology.get(); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/io/ValidatingModelProvider.java b/src/main/java/com/atomgraph/linkeddatahub/server/io/ValidatingModelProvider.java index 206228401..018bca6c2 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/io/ValidatingModelProvider.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/io/ValidatingModelProvider.java @@ -240,7 +240,7 @@ public Resource processRead(Resource resource) // this logic really belongs in a if (getApplication().isPresent() && getApplication().get().canAs(AdminApplication.class) && resource.hasProperty(RDF.type, OWL.Ontology)) { // clear cached OntModel if ontology is updated. TO-DO: send event instead - getSystem().getOntModelSpec().getDocumentManager().getFileManager().removeCacheModel(resource.getURI()); + getSystem().getRepository().remove(resource.getURI()); } if (getApplication().isPresent() && resource.hasProperty(RDF.type, ACL.Authorization)) diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImpl.java b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImpl.java index 0b8b65674..e8f1bcf8d 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImpl.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/model/impl/DocumentHierarchyGraphStoreImpl.java @@ -89,7 +89,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.jena.atlas.RuntimeIOException; import org.apache.jena.datatypes.xsd.XSDDateTime; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.rdf.model.Model; @@ -132,7 +132,7 @@ public class DocumentHierarchyGraphStoreImpl extends com.atomgraph.core.model.im public static final String UPLOADS_PATH = "uploads"; private final com.atomgraph.linkeddatahub.apps.model.Application application; - private final Ontology ontology; + private final OntModel ontology; private final Service service; private final Providers providers; private final com.atomgraph.linkeddatahub.Application system; @@ -160,7 +160,7 @@ public class DocumentHierarchyGraphStoreImpl extends com.atomgraph.core.model.im */ @Inject public DocumentHierarchyGraphStoreImpl(@Context Request request, @Context UriInfo uriInfo, MediaTypes mediaTypes, - com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service, + com.atomgraph.linkeddatahub.apps.model.Application application, Optional ontology, Optional service, @Context SecurityContext securityContext, Optional agentContext, @Context Providers providers, com.atomgraph.linkeddatahub.Application system) { @@ -1030,7 +1030,7 @@ public com.atomgraph.linkeddatahub.apps.model.Application getApplication() * * @return ontology resource */ - public Ontology getOntology() + public OntModel getOntology() { return ontology; } diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/util/OntologyModelGetter.java b/src/main/java/com/atomgraph/linkeddatahub/server/util/OntologyRepository.java similarity index 64% rename from src/main/java/com/atomgraph/linkeddatahub/server/util/OntologyModelGetter.java rename to src/main/java/com/atomgraph/linkeddatahub/server/util/OntologyRepository.java index 63c8d659d..14a0ded15 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/util/OntologyModelGetter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/util/OntologyRepository.java @@ -17,56 +17,57 @@ package com.atomgraph.linkeddatahub.server.util; import com.atomgraph.client.vocabulary.LDT; +import com.atomgraph.core.client.GraphStoreClient; +import com.atomgraph.client.util.jena.PrefixGraphRepository; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; -import com.atomgraph.server.exception.OntologyException; import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; -import org.apache.jena.ontology.OntModelSpec; +import org.apache.jena.graph.Graph; import org.apache.jena.query.ParameterizedSparqlString; import org.apache.jena.query.Query; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.ModelReader; -import org.apache.jena.util.FileManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** - * Application's ontology model getter. - * Loads ontology model using the configured ontology query + * Ontology graph repository that resolves graphs SPARQL-first: it runs the configured ontology query + * against the admin endpoint and, only if that returns nothing, falls back to the bundled mappings / + * HTTP loading of the superclass. Replaces the legacy {@code ModelGetter} plugged into {@code OntModelSpec}. + * + * @author Martynas Jusevičius {@literal } */ -public class OntologyModelGetter implements org.apache.jena.ontology.models.ModelGetter +public class OntologyRepository extends PrefixGraphRepository { - private static final Logger log = LoggerFactory.getLogger(OntologyModelGetter.class); + private static final Logger log = LoggerFactory.getLogger(OntologyRepository.class); private final EndUserApplication app; private final com.atomgraph.linkeddatahub.Application system; - private final OntModelSpec ontModelSpec; private final Query ontologyQuery; /** - * Constructs ontology getter for application. + * Constructs the repository for an application. * * @param app end-user application resource * @param system system application - * @param ontModelSpec ontology specification + * @param gsc Graph Store client for HTTP fallback loading * @param ontologyQuery SPARQL query that loads ontology terms */ - public OntologyModelGetter(EndUserApplication app, com.atomgraph.linkeddatahub.Application system, OntModelSpec ontModelSpec, Query ontologyQuery) + public OntologyRepository(EndUserApplication app, com.atomgraph.linkeddatahub.Application system, GraphStoreClient gsc, Query ontologyQuery) { + super(gsc); this.app = app; this.system = system; - this.ontModelSpec = ontModelSpec; this.ontologyQuery = ontologyQuery; } @Override - public Model getModel(String uri) + public Graph get(String uri) { - // attempt to load ontology model from the admin endpoint. TO-DO: is that necessary if ontologies terms are now stored in a single graph? + if (isCached(uri)) return super.get(uri); + + // attempt to load the ontology from the admin endpoint ParameterizedSparqlString ontologyPss = new ParameterizedSparqlString(getOntologyQuery().toString()); ontologyPss.setIri(LDT.ontology.getLocalName(), uri); @@ -82,24 +83,15 @@ public Model getModel(String uri) model = cr.readEntity(Model.class); } - if (!model.isEmpty()) return model; - - // if SPARQL result model is empty, fallback to using FileManager - FileManager fileManager = getOntModelSpec().getDocumentManager().getFileManager(); - return fileManager.loadModel(uri); - } - - @Override - public Model getModel(String uri, ModelReader loadIfAbsent) - { - try - { - return getModel(uri); - } - catch (OntologyException ex) + if (!model.isEmpty()) { - return loadIfAbsent.readModel(ModelFactory.createDefaultModel(), uri); + Graph graph = model.getGraph(); + put(uri, graph); + return graph; } + + // if the SPARQL result is empty, fall back to bundled mappings / HTTP loading + return super.get(uri); } /** @@ -122,24 +114,14 @@ public com.atomgraph.linkeddatahub.Application getSystem() return system; } - /** - * Returns ontology specification. - * - * @return ontology specification - */ - public OntModelSpec getOntModelSpec() - { - return ontModelSpec; - } - /** * Returns the SPARQL query used to load ontology terms. - * + * * @return SPARQL query */ public Query getOntologyQuery() { return ontologyQuery; } - -} \ No newline at end of file + +} diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java index 259d34a0e..c31bca208 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java @@ -16,11 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -30,8 +30,13 @@ */ public class ACL { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "http://www.w3.org/ns/auth/acl#"; @@ -49,45 +54,45 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** acl:Authorization class */ - public static final OntClass Authorization = m_model.createClass( NS + "Authorization" ); + public static final Resource Authorization = m_model.createOntClass( NS + "Authorization" ); /** acl:Read access mode */ - public static final OntClass Read = m_model.createClass( NS + "Read" ); + public static final Resource Read = m_model.createOntClass( NS + "Read" ); /** acl:Write access mode */ - public static final OntClass Write = m_model.createClass( NS + "Write" ); + public static final Resource Write = m_model.createOntClass( NS + "Write" ); /** acl:Append access mode */ - public static final OntClass Append = m_model.createClass( NS + "Append" ); + public static final Resource Append = m_model.createOntClass( NS + "Append" ); /** acl:Control access mode */ - public static final OntClass Control = m_model.createClass( NS + "Control" ); + public static final Resource Control = m_model.createOntClass( NS + "Control" ); /** acl:AuthenticatedAgent class */ - public static final OntClass AuthenticatedAgent = m_model.createClass( NS + "AuthenticatedAgent" ); + public static final Resource AuthenticatedAgent = m_model.createOntClass( NS + "AuthenticatedAgent" ); /** acl:delegates property **/ - public static final ObjectProperty delegates = m_model.createObjectProperty( NS + "delegates" ); + public static final Property delegates = m_model.createObjectProperty( NS + "delegates" ); /** acl:owner property */ - public static final ObjectProperty owner = m_model.createObjectProperty( NS + "owner" ); + public static final Property owner = m_model.createObjectProperty( NS + "owner" ); /** acl:agent property */ - public static final ObjectProperty agent = m_model.createObjectProperty( NS + "agent" ); + public static final Property agent = m_model.createObjectProperty( NS + "agent" ); /** acl:agentClass property */ - public static final ObjectProperty agentClass = m_model.createObjectProperty( NS + "agentClass" ); + public static final Property agentClass = m_model.createObjectProperty( NS + "agentClass" ); /** acl:agentGroup property */ - public static final ObjectProperty agentGroup = m_model.createObjectProperty( NS + "agentGroup" ); + public static final Property agentGroup = m_model.createObjectProperty( NS + "agentGroup" ); /** acl:mode property */ - public static final ObjectProperty mode = m_model.createObjectProperty( NS + "mode" ); + public static final Property mode = m_model.createObjectProperty( NS + "mode" ); /** acl:accessTo property */ - public static final ObjectProperty accessTo = m_model.createObjectProperty( NS + "accessTo" ); + public static final Property accessTo = m_model.createObjectProperty( NS + "accessTo" ); /** acl:accessToClass property */ - public static final ObjectProperty accessToClass = m_model.createObjectProperty( NS + "accessToClass" ); + public static final Property accessToClass = m_model.createObjectProperty( NS + "accessToClass" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Admin.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Admin.java index d2bda9453..8c463d9ca 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Admin.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Admin.java @@ -16,9 +16,10 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; + import org.apache.jena.rdf.model.Resource; /** @@ -28,9 +29,14 @@ */ public class Admin { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/admin#"; diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Cert.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Cert.java index c0906f376..ad869bae6 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Cert.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Cert.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -32,8 +31,13 @@ public class Cert { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "http://www.w3.org/ns/auth/cert#"; @@ -50,18 +54,18 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Public key class */ - public static final OntClass PublicKey = m_model.createClass(NS + "PublicKey"); + public static final Resource PublicKey = m_model.createOntClass(NS + "PublicKey"); /** RSA public key class */ - public static final OntClass RSAPublicKey = m_model.createClass(NS + "RSAPublicKey"); + public static final Resource RSAPublicKey = m_model.createOntClass(NS + "RSAPublicKey"); /** Key property */ - public static final ObjectProperty key = m_model.createObjectProperty( NS + "key" ); + public static final Property key = m_model.createObjectProperty( NS + "key" ); /** Modulus property */ - public static final DatatypeProperty modulus = m_model.createDatatypeProperty( NS + "modulus" ); + public static final Property modulus = m_model.createDataProperty( NS + "modulus" ); /** Exponent property */ - public static final DatatypeProperty exponent = m_model.createDatatypeProperty( NS + "exponent" ); + public static final Property exponent = m_model.createDataProperty( NS + "exponent" ); } \ No newline at end of file diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/DH.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/DH.java index 7ea41b305..140b2e511 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/DH.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/DH.java @@ -15,12 +15,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -31,8 +30,13 @@ public class DH { + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } + /**

The RDF model that holds the vocabulary terms

*/ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /**

The namespace of the vocabulary as a string

*/ public static final String NS = "https://www.w3.org/ns/ldt/document-hierarchy#"; @@ -49,15 +53,15 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Document class */ - public static final OntClass Document = m_model.createClass( NS + "Document" ); + public static final Resource Document = m_model.createOntClass( NS + "Document" ); /** Container class */ - public static final OntClass Container = m_model.createClass( NS + "Container" ); + public static final Resource Container = m_model.createOntClass( NS + "Container" ); /** Item class */ - public static final OntClass Item = m_model.createClass( NS + "Item" ); + public static final Resource Item = m_model.createOntClass( NS + "Item" ); /** Slug property */ - public static final DatatypeProperty slug = m_model.createDatatypeProperty( NS + "slug" ); + public static final Property slug = m_model.createDataProperty( NS + "slug" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Default.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Default.java index 03d36f429..47ba21532 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Default.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Default.java @@ -16,10 +16,10 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; + import org.apache.jena.rdf.model.Resource; /** @@ -29,8 +29,13 @@ */ public class Default { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/default#"; @@ -50,6 +55,6 @@ public static String getURI() // DOMAIN /** Root document class */ - public static final OntClass Root = m_model.createClass(NS + "Root"); + public static final Resource Root = m_model.createOntClass(NS + "Root"); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/FOAF.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/FOAF.java index a71875fe5..c2bfcc475 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/FOAF.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/FOAF.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -31,8 +30,13 @@ */ public class FOAF { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "http://xmlns.com/foaf/0.1/"; @@ -52,45 +56,45 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Agent class */ - public static final OntClass Agent = m_model.createClass( NS + "Agent" ); + public static final Resource Agent = m_model.createOntClass( NS + "Agent" ); /** Person class */ - public static final OntClass Person = m_model.createClass( NS + "Person" ); + public static final Resource Person = m_model.createOntClass( NS + "Person" ); /** Document class */ - public static final OntClass Document = m_model.createClass( NS + "Document" ); + public static final Resource Document = m_model.createOntClass( NS + "Document" ); /** Name property */ - public static final DatatypeProperty name = m_model.createDatatypeProperty( NS + "name" ); + public static final Property name = m_model.createDataProperty( NS + "name" ); /** Given name property */ - public static final DatatypeProperty givenName = m_model.createDatatypeProperty( NS + "givenName" ); + public static final Property givenName = m_model.createDataProperty( NS + "givenName" ); /** Family name property */ - public static final DatatypeProperty familyName = m_model.createDatatypeProperty( NS + "familyName" ); + public static final Property familyName = m_model.createDataProperty( NS + "familyName" ); /** Mailbox property */ - public static final ObjectProperty mbox = m_model.createObjectProperty( NS + "mbox" ); + public static final Property mbox = m_model.createObjectProperty( NS + "mbox" ); /** Based near property */ - public static final ObjectProperty based_near = m_model.createObjectProperty( NS + "based_near" ); + public static final Property based_near = m_model.createObjectProperty( NS + "based_near" ); /** Member property */ - public static final ObjectProperty member = m_model.createObjectProperty( NS + "member" ); + public static final Property member = m_model.createObjectProperty( NS + "member" ); /** Primary topic property */ - public static final ObjectProperty primaryTopic = m_model.createObjectProperty( NS + "primaryTopic" ); + public static final Property primaryTopic = m_model.createObjectProperty( NS + "primaryTopic" ); /** Is primary topic of property */ - public static final ObjectProperty isPrimaryTopicOf = m_model.createObjectProperty( NS + "isPrimaryTopicOf" ); + public static final Property isPrimaryTopicOf = m_model.createObjectProperty( NS + "isPrimaryTopicOf" ); /** Maker property */ - public static final ObjectProperty maker = m_model.createObjectProperty( NS + "maker" ); + public static final Property maker = m_model.createObjectProperty( NS + "maker" ); /** Account property */ - public static final ObjectProperty account = m_model.createObjectProperty( NS + "account" ); + public static final Property account = m_model.createObjectProperty( NS + "account" ); /** Image property */ - public static final ObjectProperty img = m_model.createObjectProperty( NS + "img" ); + public static final Property img = m_model.createObjectProperty( NS + "img" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Google.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Google.java index cd68c45f5..6a21142b4 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Google.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/Google.java @@ -1,9 +1,10 @@ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -14,8 +15,13 @@ public class Google { + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } + /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/services/google#"; @@ -35,9 +41,9 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Client ID property */ - public static final DatatypeProperty clientID = m_model.createDatatypeProperty( NS + "clientID" ); + public static final Property clientID = m_model.createDataProperty( NS + "clientID" ); /** Client secret property */ - public static final DatatypeProperty clientSecret = m_model.createDatatypeProperty( NS + "clientSecret" ); + public static final Property clientSecret = m_model.createDataProperty( NS + "clientSecret" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LACL.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LACL.java index fd2af9e58..bf4123de9 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LACL.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LACL.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -31,8 +30,13 @@ */ public class LACL { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/admin/acl#"; @@ -52,33 +56,33 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Authorization request class */ - public static final OntClass AuthorizationRequest = m_model.createClass( NS + "AuthorizationRequest" ); + public static final Resource AuthorizationRequest = m_model.createOntClass( NS + "AuthorizationRequest" ); /** Authorization request class */ - public static final OntClass OwnerAuthorization = m_model.createClass( NS + "OwnerAuthorization" ); + public static final Resource OwnerAuthorization = m_model.createOntClass( NS + "OwnerAuthorization" ); /** Password property */ - public static final DatatypeProperty password = m_model.createDatatypeProperty( NS + "password" ); + public static final Property password = m_model.createDataProperty( NS + "password" ); /** Issuer property */ - public static final DatatypeProperty issuer = m_model.createDatatypeProperty( NS + "issuer" ); + public static final Property issuer = m_model.createDataProperty( NS + "issuer" ); /** Request agent property **/ - public static final ObjectProperty requestMode = m_model.createObjectProperty( NS + "requestMode" ); + public static final Property requestMode = m_model.createObjectProperty( NS + "requestMode" ); /** Request agent property **/ - public static final ObjectProperty requestAgent = m_model.createObjectProperty( NS + "requestAgent" ); + public static final Property requestAgent = m_model.createObjectProperty( NS + "requestAgent" ); /** Request agent group property **/ - public static final ObjectProperty requestAgentGroup = m_model.createObjectProperty( NS + "requestAgentGroup" ); + public static final Property requestAgentGroup = m_model.createObjectProperty( NS + "requestAgentGroup" ); /** Request access to property */ - public static final ObjectProperty requestAccessTo = m_model.createObjectProperty( NS + "requestAccessTo" ); + public static final Property requestAccessTo = m_model.createObjectProperty( NS + "requestAccessTo" ); /** Request access to class property */ - public static final ObjectProperty requestAccessToClass = m_model.createObjectProperty( NS + "requestAccessToClass" ); + public static final Property requestAccessToClass = m_model.createObjectProperty( NS + "requestAccessToClass" ); /** Request access property */ - public static final ObjectProperty requestAccess = m_model.createObjectProperty( NS + "requestAccess" ); + public static final Property requestAccess = m_model.createObjectProperty( NS + "requestAccess" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LAPP.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LAPP.java index 7ef0a28f1..9e991e04f 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LAPP.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LAPP.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -32,8 +31,13 @@ public class LAPP { + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } + /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/apps#"; @@ -53,45 +57,45 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Application class */ - public static final OntClass Context = m_model.createClass( NS + "Context" ); + public static final Resource Context = m_model.createOntClass( NS + "Context" ); /** Dataset class */ - public static final OntClass Dataset = m_model.createClass( NS + "Dataset" ); + public static final Resource Dataset = m_model.createOntClass( NS + "Dataset" ); /** Application class */ - public static final OntClass Application = m_model.createClass( NS + "Application" ); + public static final Resource Application = m_model.createOntClass( NS + "Application" ); /** Admin application class */ - public static final OntClass AdminApplication = m_model.createClass( NS + "AdminApplication" ); + public static final Resource AdminApplication = m_model.createOntClass( NS + "AdminApplication" ); /** End-user application class */ - public static final OntClass EndUserApplication = m_model.createClass( NS + "EndUserApplication" ); + public static final Resource EndUserApplication = m_model.createOntClass( NS + "EndUserApplication" ); /** Package class */ - public static final OntClass Package = m_model.createClass( NS + "Package" ); + public static final Resource Package = m_model.createOntClass( NS + "Package" ); /** Admin application class */ -// public static final ObjectProperty adminApplication = m_model.createObjectProperty( NS + "adminApplication" ); +// public static final Property adminApplication = m_model.createObjectProperty( NS + "adminApplication" ); // // /** End-user application class */ -// public static final ObjectProperty endUserApplication = m_model.createObjectProperty( NS + "endUserApplication" ); +// public static final Property endUserApplication = m_model.createObjectProperty( NS + "endUserApplication" ); /** Frontend proxy property */ - public static final ObjectProperty frontendProxy = m_model.createObjectProperty( NS + "frontendProxy" ); + public static final Property frontendProxy = m_model.createObjectProperty( NS + "frontendProxy" ); /** Backend proxy property */ - public static final ObjectProperty backendProxy = m_model.createObjectProperty( NS + "backendProxy" ); + public static final Property backendProxy = m_model.createObjectProperty( NS + "backendProxy" ); /** Prefix property */ - public static final ObjectProperty prefix = m_model.createObjectProperty( NS + "prefix" ); + public static final Property prefix = m_model.createObjectProperty( NS + "prefix" ); /** Read-only property */ - public static final DatatypeProperty allowRead = m_model.createDatatypeProperty( NS + "allowRead" ); + public static final Property allowRead = m_model.createDataProperty( NS + "allowRead" ); /** Origin property for subdomain-based application matching */ - public static final ObjectProperty origin = m_model.createObjectProperty(NS + "origin"); + public static final Property origin = m_model.createObjectProperty(NS + "origin"); /** Application property (for Link header rel) */ - public static final ObjectProperty application = m_model.createObjectProperty( NS + "application" ); + public static final Property application = m_model.createObjectProperty( NS + "application" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDH.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDH.java index 629fe4d4a..a55c1fbce 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDH.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDH.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -31,8 +30,13 @@ */ public class LDH { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub#"; @@ -51,62 +55,62 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Dataset class */ - public static final OntClass Dataset = m_model.createClass(NS + "Dataset"); + public static final Resource Dataset = m_model.createOntClass(NS + "Dataset"); /** Generic service class */ - public static final OntClass GenericService = m_model.createClass(NS + "GenericService"); + public static final Resource GenericService = m_model.createOntClass(NS + "GenericService"); /** Import class */ - public static final OntClass Import = m_model.createClass(NS + "Import"); + public static final Resource Import = m_model.createOntClass(NS + "Import"); /** CSV import class */ - public static final OntClass CSVImport = m_model.createClass(NS + "CSVImport"); + public static final Resource CSVImport = m_model.createOntClass(NS + "CSVImport"); /** RDF import class */ - public static final OntClass RDFImport = m_model.createClass(NS + "RDFImport"); + public static final Resource RDFImport = m_model.createOntClass(NS + "RDFImport"); /** File class */ - public static final OntClass File = m_model.createClass(NS + "File"); + public static final Resource File = m_model.createOntClass(NS + "File"); /** Object class */ - public static final OntClass Object = m_model.createClass(NS + "Object"); + public static final Resource Object = m_model.createOntClass(NS + "Object"); /** View class */ - public static final OntClass View = m_model.createClass(NS + "View"); + public static final Resource View = m_model.createOntClass(NS + "View"); /** URI syntax violation class */ - public static final OntClass URISyntaxViolation = m_model.createClass(NS + "URISyntaxViolation"); + public static final Resource URISyntaxViolation = m_model.createOntClass(NS + "URISyntaxViolation"); /** Base property */ - public static final ObjectProperty base = m_model.createObjectProperty( NS + "base" ); + public static final Property base = m_model.createObjectProperty( NS + "base" ); /** File property */ - public static final ObjectProperty file = m_model.createObjectProperty( NS + "file" ); + public static final Property file = m_model.createObjectProperty( NS + "file" ); /** Action property */ - public static final ObjectProperty action = m_model.createObjectProperty( NS + "action" ); + public static final Property action = m_model.createObjectProperty( NS + "action" ); /** Delimiter property */ - public static final DatatypeProperty delimiter = m_model.createDatatypeProperty( NS + "delimiter" ); + public static final Property delimiter = m_model.createDataProperty( NS + "delimiter" ); /** Violation value property */ - public static final DatatypeProperty violationValue = m_model.createDatatypeProperty( NS + "violationValue" ); + public static final Property violationValue = m_model.createDataProperty( NS + "violationValue" ); /** Request URI property */ - public static final ObjectProperty requestUri = m_model.createObjectProperty(NS + "requestUri"); + public static final Property requestUri = m_model.createObjectProperty(NS + "requestUri"); /** HTTP headers property */ - public static final ObjectProperty httpHeaders = m_model.createObjectProperty(NS + "httpHeaders"); + public static final Property httpHeaders = m_model.createObjectProperty(NS + "httpHeaders"); /** Service property */ - public static final ObjectProperty service = m_model.createObjectProperty( NS + "service" ); + public static final Property service = m_model.createObjectProperty( NS + "service" ); /** * For shape property */ - public static final ObjectProperty forShape = m_model.createObjectProperty( NS + "forShape" ); + public static final Property forShape = m_model.createObjectProperty( NS + "forShape" ); /** * Import property - used to import packages into an application */ - public static final ObjectProperty importPackage = m_model.createObjectProperty( NS + "import" ); + public static final Property importPackage = m_model.createObjectProperty( NS + "import" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java index 5daf5c389..e808098ae 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java @@ -16,11 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -30,8 +30,13 @@ */ public class LDHC { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/config#"; @@ -51,138 +56,138 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Base URI property */ - public static final ObjectProperty baseUri = m_model.createObjectProperty( NS + "baseUri" ); + public static final Property baseUri = m_model.createObjectProperty( NS + "baseUri" ); /** Proxy scheme property */ - public static final DatatypeProperty proxyScheme = m_model.createDatatypeProperty( NS + "proxyScheme" ); + public static final Property proxyScheme = m_model.createDataProperty( NS + "proxyScheme" ); /** Proxy host property */ - public static final DatatypeProperty proxyHost = m_model.createDatatypeProperty( NS + "proxyHost" ); + public static final Property proxyHost = m_model.createDataProperty( NS + "proxyHost" ); /** Proxy port property */ - public static final DatatypeProperty proxyPort = m_model.createDatatypeProperty( NS + "proxyPort" ); + public static final Property proxyPort = m_model.createDataProperty( NS + "proxyPort" ); /** Document type query property */ - public static final DatatypeProperty documentTypeQuery = m_model.createDatatypeProperty( NS + "documentTypeQuery" ); + public static final Property documentTypeQuery = m_model.createDataProperty( NS + "documentTypeQuery" ); /** Document owner query property */ - public static final DatatypeProperty documentOwnerQuery = m_model.createDatatypeProperty( NS + "documentOwnerQuery" ); + public static final Property documentOwnerQuery = m_model.createDataProperty( NS + "documentOwnerQuery" ); /** ACL query property */ - public static final DatatypeProperty aclQuery = m_model.createDatatypeProperty( NS + "aclQuery" ); + public static final Property aclQuery = m_model.createDataProperty( NS + "aclQuery" ); /** Owner's ACL property */ - public static final DatatypeProperty ownerAclQuery = m_model.createDatatypeProperty( NS + "ownerAclQuery" ); + public static final Property ownerAclQuery = m_model.createDataProperty( NS + "ownerAclQuery" ); /** WebID query property */ - public static final DatatypeProperty webIDQuery = m_model.createDatatypeProperty( NS + "webIDQuery" ); + public static final Property webIDQuery = m_model.createDataProperty( NS + "webIDQuery" ); /** Agent query property */ - public static final DatatypeProperty agentQuery = m_model.createDatatypeProperty( NS + "agentQuery" ); + public static final Property agentQuery = m_model.createDataProperty( NS + "agentQuery" ); /** User account query property */ - public static final DatatypeProperty userAccountQuery = m_model.createDatatypeProperty( NS + "userAccountQuery" ); + public static final Property userAccountQuery = m_model.createDataProperty( NS + "userAccountQuery" ); /** Ontology query property */ - public static final DatatypeProperty ontologyQuery = m_model.createDatatypeProperty( NS + "ontologyQuery" ); + public static final Property ontologyQuery = m_model.createDataProperty( NS + "ontologyQuery" ); /** Upload root property */ - public static final ObjectProperty uploadRoot = m_model.createObjectProperty( NS + "uploadRoot" ); + public static final Property uploadRoot = m_model.createObjectProperty( NS + "uploadRoot" ); /** Invalidate cache property */ - public static final DatatypeProperty invalidateCache = m_model.createDatatypeProperty( NS + "invalidateCache" ); + public static final Property invalidateCache = m_model.createDataProperty( NS + "invalidateCache" ); /** Cookie max age property */ - public static final DatatypeProperty cookieMaxAge = m_model.createDatatypeProperty( NS + "cookieMaxAge" ); + public static final Property cookieMaxAge = m_model.createDataProperty( NS + "cookieMaxAge" ); /** Client keystore property */ - public static final ObjectProperty clientKeyStore = m_model.createObjectProperty( NS + "clientKeyStore" ); + public static final Property clientKeyStore = m_model.createObjectProperty( NS + "clientKeyStore" ); /** Client keystore password property */ - public static final DatatypeProperty clientKeyStorePassword = m_model.createDatatypeProperty( NS + "clientKeyStorePassword" ); + public static final Property clientKeyStorePassword = m_model.createDataProperty( NS + "clientKeyStorePassword" ); /** Secretary cert alias property */ - public static final DatatypeProperty secretaryCertAlias = m_model.createDatatypeProperty( NS + "secretaryCertAlias" ); + public static final Property secretaryCertAlias = m_model.createDataProperty( NS + "secretaryCertAlias" ); /** Client truststore property */ - public static final ObjectProperty clientTrustStore = m_model.createObjectProperty( NS + "clientTrustStore" ); + public static final Property clientTrustStore = m_model.createObjectProperty( NS + "clientTrustStore" ); /** Client truststore password property */ - public static final DatatypeProperty clientTrustStorePassword = m_model.createDatatypeProperty( NS + "clientTrustStorePassword" ); + public static final Property clientTrustStorePassword = m_model.createDataProperty( NS + "clientTrustStorePassword" ); /** Signup email subject property */ - public static final DatatypeProperty signUpEMailSubject = m_model.createDatatypeProperty( NS + "signUpEMailSubject" ); + public static final Property signUpEMailSubject = m_model.createDataProperty( NS + "signUpEMailSubject" ); /** WebID signup email text property */ - public static final DatatypeProperty webIDSignUpEMailText = m_model.createDatatypeProperty( NS + "webIDSignUpEMailText" ); + public static final Property webIDSignUpEMailText = m_model.createDataProperty( NS + "webIDSignUpEMailText" ); /** OAuth signup email text property */ - public static final DatatypeProperty oAuthSignUpEMailText = m_model.createDatatypeProperty( NS + "oAuthSignUpEMailText" ); + public static final Property oAuthSignUpEMailText = m_model.createDataProperty( NS + "oAuthSignUpEMailText" ); /** Notification address property */ - public static final DatatypeProperty notificationAddress = m_model.createDatatypeProperty( NS + "notificationAddress" ); + public static final Property notificationAddress = m_model.createDataProperty( NS + "notificationAddress" ); /** Request access email subject property */ - public static final DatatypeProperty requestAccessEMailSubject = m_model.createDatatypeProperty( NS + "requestAccessEMailSubject" ); + public static final Property requestAccessEMailSubject = m_model.createDataProperty( NS + "requestAccessEMailSubject" ); /** Request access email text property */ - public static final DatatypeProperty requestAccessEMailText = m_model.createDatatypeProperty( NS + "requestAccessEMailText" ); + public static final Property requestAccessEMailText = m_model.createDataProperty( NS + "requestAccessEMailText" ); /** Authorization email subject property */ - public static final DatatypeProperty authorizationEMailSubject = m_model.createDatatypeProperty( NS + "authorizationEMailSubject" ); + public static final Property authorizationEMailSubject = m_model.createDataProperty( NS + "authorizationEMailSubject" ); /** Authorization email text property */ - public static final DatatypeProperty authorizationEMailText = m_model.createDatatypeProperty( NS + "authorizationEMailText" ); + public static final Property authorizationEMailText = m_model.createDataProperty( NS + "authorizationEMailText" ); /** Signup cert validity property */ - public static final DatatypeProperty signUpCertValidity = m_model.createDatatypeProperty( NS + "signUpCertValidity" ); + public static final Property signUpCertValidity = m_model.createDataProperty( NS + "signUpCertValidity" ); /** Context dataset property */ - public static final ObjectProperty contextDataset = m_model.createObjectProperty( NS + "contextDataset" ); + public static final Property contextDataset = m_model.createObjectProperty( NS + "contextDataset" ); /** Max connections per route property */ - public static final DatatypeProperty maxConnPerRoute = m_model.createDatatypeProperty( NS + "maxConnPerRoute" ); + public static final Property maxConnPerRoute = m_model.createDataProperty( NS + "maxConnPerRoute" ); /** Max total connections property */ - public static final DatatypeProperty maxTotalConn = m_model.createDatatypeProperty( NS + "maxTotalConn" ); + public static final Property maxTotalConn = m_model.createDataProperty( NS + "maxTotalConn" ); /** Import keep-alive property */ - public static final DatatypeProperty importKeepAlive = m_model.createDatatypeProperty( NS + "importKeepAlive" ); + public static final Property importKeepAlive = m_model.createDataProperty( NS + "importKeepAlive" ); /** HTTP client request retry count */ - public static final DatatypeProperty maxRequestRetries = m_model.createDatatypeProperty( NS + "maxRequestRetries" ); + public static final Property maxRequestRetries = m_model.createDataProperty( NS + "maxRequestRetries" ); /** Timeout in milliseconds waiting for a connection from the HTTP client pool */ - public static final DatatypeProperty connectionRequestTimeout = m_model.createDatatypeProperty( NS + "connectionRequestTimeout" ); + public static final Property connectionRequestTimeout = m_model.createDataProperty( NS + "connectionRequestTimeout" ); /** Max content length property */ - public static final DatatypeProperty maxContentLength = m_model.createDatatypeProperty( NS + "maxContentLength" ); + public static final Property maxContentLength = m_model.createDataProperty( NS + "maxContentLength" ); /** Support languages property */ - public static final DatatypeProperty supportedLanguages = m_model.createDatatypeProperty( NS + "supportedLanguages" ); + public static final Property supportedLanguages = m_model.createDataProperty( NS + "supportedLanguages" ); /** Max import threads property */ - public static final DatatypeProperty maxImportThreads = m_model.createDatatypeProperty( NS + "maxImportThreads" ); + public static final Property maxImportThreads = m_model.createDataProperty( NS + "maxImportThreads" ); /** Enable WebID signup property **/ - public static final DatatypeProperty enableWebIDSignUp = m_model.createDatatypeProperty( NS + "enableWebIDSignUp" ); + public static final Property enableWebIDSignUp = m_model.createDataProperty( NS + "enableWebIDSignUp" ); /** Enable Linked Data proxy property */ - public static final DatatypeProperty enableLinkedDataProxy = m_model.createDatatypeProperty( NS + "enableLinkedDataProxy" ); + public static final Property enableLinkedDataProxy = m_model.createDataProperty( NS + "enableLinkedDataProxy" ); /** Allow internal URLs property */ - public static final DatatypeProperty allowInternalUrls = m_model.createDatatypeProperty( NS + "allowInternalUrls" ); + public static final Property allowInternalUrls = m_model.createDataProperty( NS + "allowInternalUrls" ); /** OIDC refresh token properties property */ - public static final DatatypeProperty oidcRefreshTokens = m_model.createDatatypeProperty( NS + "oidcRefreshTokens" ); + public static final Property oidcRefreshTokens = m_model.createDataProperty( NS + "oidcRefreshTokens" ); /** Frontend proxy URI property (Varnish frontend cache, used for cache invalidation) */ - public static final ObjectProperty frontendProxy = m_model.createObjectProperty( NS + "frontendProxy" ); + public static final Property frontendProxy = m_model.createObjectProperty( NS + "frontendProxy" ); /** Backend proxy URI for the admin SPARQL service (used for cache invalidation and endpoint URI rewriting) */ - public static final ObjectProperty backendProxyAdmin = m_model.createObjectProperty( NS + "backendProxyAdmin" ); + public static final Property backendProxyAdmin = m_model.createObjectProperty( NS + "backendProxyAdmin" ); /** Backend proxy URI for the end-user SPARQL service (used for cache invalidation and endpoint URI rewriting) */ - public static final ObjectProperty backendProxyEndUser = m_model.createObjectProperty( NS + "backendProxyEndUser" ); + public static final Property backendProxyEndUser = m_model.createObjectProperty( NS + "backendProxyEndUser" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHT.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHT.java index e43185459..e2fdfc418 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHT.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHT.java @@ -16,11 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -31,8 +31,13 @@ @Deprecated public class LDHT { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/templates#"; @@ -52,9 +57,9 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** For class property */ - //public static final ObjectProperty forClass = m_model.createObjectProperty( NS + "forClass" ); + //public static final Property forClass = m_model.createObjectProperty( NS + "forClass" ); /** Ban property */ - public static final DatatypeProperty ban = m_model.createDatatypeProperty( NS + "ban" ); + public static final Property ban = m_model.createDataProperty( NS + "ban" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/NFO.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/NFO.java index 6aba1781b..d828638b6 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/NFO.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/NFO.java @@ -16,11 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -30,8 +30,13 @@ */ public class NFO { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#"; @@ -51,9 +56,9 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** File data object class */ - public static final OntClass FileDataObject = m_model.createClass(NS + "FileDataObject"); + public static final Resource FileDataObject = m_model.createOntClass(NS + "FileDataObject"); /** Filename property */ - public static final DatatypeProperty fileName = m_model.createDatatypeProperty( NS + "fileName" ); + public static final Property fileName = m_model.createDataProperty( NS + "fileName" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ORCID.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ORCID.java index 7998fc907..c64923c8e 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ORCID.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ORCID.java @@ -1,9 +1,10 @@ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -14,8 +15,13 @@ public class ORCID { + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } + /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "https://w3id.org/atomgraph/linkeddatahub/services/orcid#"; @@ -35,9 +41,9 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Client ID property */ - public static final DatatypeProperty clientID = m_model.createDatatypeProperty( NS + "clientID" ); + public static final Property clientID = m_model.createDataProperty( NS + "clientID" ); /** Client secret property */ - public static final DatatypeProperty clientSecret = m_model.createDatatypeProperty( NS + "clientSecret" ); + public static final Property clientSecret = m_model.createDataProperty( NS + "clientSecret" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/PROV.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/PROV.java index efd61c669..fb4655c53 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/PROV.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/PROV.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -30,9 +29,14 @@ * @author Martynas Jusevičius {@literal } */ public class PROV { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "http://www.w3.org/ns/prov#"; @@ -52,33 +56,33 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Entity class */ - public static final OntClass Entity = m_model.createClass( NS + "Entity" ); + public static final Resource Entity = m_model.createOntClass( NS + "Entity" ); /** Activity class */ - public static final OntClass Activity = m_model.createClass( NS + "Activity" ); + public static final Resource Activity = m_model.createOntClass( NS + "Activity" ); /** Agent class */ - public static final OntClass Agent = m_model.createClass( NS + "Agent" ); + public static final Resource Agent = m_model.createOntClass( NS + "Agent" ); /** Was attributed to property */ - public static final ObjectProperty wasAttributedTo = m_model.createObjectProperty( NS + "wasAttributedTo" ); + public static final Property wasAttributedTo = m_model.createObjectProperty( NS + "wasAttributedTo" ); /** Was derived from property */ - public static final ObjectProperty wasDerivedFrom = m_model.createObjectProperty( NS + "wasDerivedFrom" ); + public static final Property wasDerivedFrom = m_model.createObjectProperty( NS + "wasDerivedFrom" ); /** Was generated by property */ - public static final ObjectProperty wasGeneratedBy = m_model.createObjectProperty( NS + "wasGeneratedBy" ); + public static final Property wasGeneratedBy = m_model.createObjectProperty( NS + "wasGeneratedBy" ); /** Was started by property */ - public static final ObjectProperty wasStartedBy = m_model.createObjectProperty( NS + "wasStartedBy" ); + public static final Property wasStartedBy = m_model.createObjectProperty( NS + "wasStartedBy" ); /** Started at time property */ - public static final DatatypeProperty startedAtTime = m_model.createDatatypeProperty( NS + "startedAtTime" ); + public static final Property startedAtTime = m_model.createDataProperty( NS + "startedAtTime" ); /** Ended at time property */ - public static final DatatypeProperty endedAtTime = m_model.createDatatypeProperty( NS + "endedAtTime" ); + public static final Property endedAtTime = m_model.createDataProperty( NS + "endedAtTime" ); /** Generated at time property */ - public static final DatatypeProperty generatedAtTime = m_model.createDatatypeProperty( NS + "generatedAtTime" ); + public static final Property generatedAtTime = m_model.createDataProperty( NS + "generatedAtTime" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/SIOC.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/SIOC.java index 302ef9f39..6e2f11f0b 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/SIOC.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/SIOC.java @@ -16,8 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.*; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -27,12 +30,17 @@ */ public class SIOC { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** *

* The ontology model that holds the vocabulary terms *

*/ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** *

@@ -74,7 +82,7 @@ public static String getURI() { * etc. *

*/ - public static final ObjectProperty ABOUT = m_model + public static final Property ABOUT = m_model .createObjectProperty("http://rdfs.org/sioc/ns#about"); /** @@ -82,7 +90,7 @@ public static String getURI() { * Refers to the foaf:Agent or foaf:Person who owns this sioc:User online account. *

*/ - public static final ObjectProperty ACCOUNT_OF = m_model + public static final Property ACCOUNT_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#account_of"); /** @@ -90,7 +98,7 @@ public static String getURI() { * A Site that the User is an administrator of. *

*/ - public static final ObjectProperty ADMINISTRATOR_OF = m_model + public static final Property ADMINISTRATOR_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#administrator_of"); /** @@ -98,7 +106,7 @@ public static String getURI() { * The URI of a file attached to an Item. *

*/ - public static final ObjectProperty ATTACHMENT = m_model + public static final Property ATTACHMENT = m_model .createObjectProperty("http://rdfs.org/sioc/ns#attachment"); /** @@ -106,7 +114,7 @@ public static String getURI() { * An image or depiction used to represent this User. *

*/ - public static final ObjectProperty AVATAR = m_model + public static final Property AVATAR = m_model .createObjectProperty("http://rdfs.org/sioc/ns#avatar"); /** @@ -114,7 +122,7 @@ public static String getURI() { * An Item that this Container contains. *

*/ - public static final ObjectProperty CONTAINER_OF = m_model + public static final Property CONTAINER_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#container_of"); /** @@ -122,7 +130,7 @@ public static String getURI() { * A resource that the User is a creator of. *

*/ - public static final ObjectProperty CREATOR_OF = m_model + public static final Property CREATOR_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#creator_of"); /** @@ -130,7 +138,7 @@ public static String getURI() { * An electronic mail address of the User. *

*/ - public static final ObjectProperty EMAIL = m_model + public static final Property EMAIL = m_model .createObjectProperty("http://rdfs.org/sioc/ns#email"); /** @@ -138,7 +146,7 @@ public static String getURI() { * A feed (e.g. RSS, Atom, etc.) pertaining to this resource (e.g. for a Forum, Site, User, etc.). *

*/ - public static final ObjectProperty FEED = m_model + public static final Property FEED = m_model .createObjectProperty("http://rdfs.org/sioc/ns#feed"); /** @@ -147,7 +155,7 @@ public static String getURI() { * updates). *

*/ - public static final ObjectProperty FOLLOWS = m_model + public static final Property FOLLOWS = m_model .createObjectProperty("http://rdfs.org/sioc/ns#follows"); /** @@ -155,13 +163,13 @@ public static String getURI() { * A User who has this Role. *

*/ - public static final ObjectProperty FUNCTION_OF = m_model + public static final Property FUNCTION_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#function_of"); /** * This property has been renamed. Use sioc:sioc:usergroup_of instead. */ - public static final ObjectProperty GROUP_OF = m_model + public static final Property GROUP_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#group_of"); /** @@ -169,7 +177,7 @@ public static String getURI() { * A User who is an administrator of this Site. *

*/ - public static final ObjectProperty HAS_ADMINISTRATOR = m_model + public static final Property HAS_ADMINISTRATOR = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_administrator"); /** @@ -177,7 +185,7 @@ public static String getURI() { * The Container to which this Item belongs. *

*/ - public static final ObjectProperty HAS_CONTAINER = m_model + public static final Property HAS_CONTAINER = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_container"); /** @@ -185,7 +193,7 @@ public static String getURI() { * This is the User who made this resource. *

*/ - public static final ObjectProperty HAS_CREATOR = m_model + public static final Property HAS_CREATOR = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_creator"); /** @@ -193,7 +201,7 @@ public static String getURI() { * The discussion that is related to this Item. *

*/ - public static final ObjectProperty HAS_DISCUSSION = m_model + public static final Property HAS_DISCUSSION = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_discussion"); /** @@ -201,13 +209,13 @@ public static String getURI() { * A Role that this User has. *

*/ - public static final ObjectProperty HAS_FUNCTION = m_model + public static final Property HAS_FUNCTION = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_function"); /** * This property has been renamed. Use sioc:has_usergroup instead. */ - public static final ObjectProperty HAS_GROUP = m_model + public static final Property HAS_GROUP = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_group"); /** @@ -215,7 +223,7 @@ public static String getURI() { * The Site that hosts this Forum. *

*/ - public static final ObjectProperty HAS_HOST = m_model + public static final Property HAS_HOST = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_host"); /** @@ -223,7 +231,7 @@ public static String getURI() { * A User who is a member of this Usergroup. *

*/ - public static final ObjectProperty HAS_MEMBER = m_model + public static final Property HAS_MEMBER = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_member"); /** @@ -231,7 +239,7 @@ public static String getURI() { * A User who is a moderator of this Forum. *

*/ - public static final ObjectProperty HAS_MODERATOR = m_model + public static final Property HAS_MODERATOR = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_moderator"); /** @@ -239,7 +247,7 @@ public static String getURI() { * A User who modified this Item. *

*/ - public static final ObjectProperty HAS_MODIFIER = m_model + public static final Property HAS_MODIFIER = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_modifier"); /** @@ -247,7 +255,7 @@ public static String getURI() { * A User that this resource is owned by. *

*/ - public static final ObjectProperty HAS_OWNER = m_model + public static final Property HAS_OWNER = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_owner"); /** @@ -255,7 +263,7 @@ public static String getURI() { * A Container or Forum that this Container or Forum is a child of. *

*/ - public static final ObjectProperty HAS_PARENT = m_model + public static final Property HAS_PARENT = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_parent"); /** @@ -263,7 +271,7 @@ public static String getURI() { * An resource that is a part of this subject. *

*/ - public static final ObjectProperty HAS_PART = m_model + public static final Property HAS_PART = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_part"); /** @@ -271,7 +279,7 @@ public static String getURI() { * Points to an Item or Post that is a reply or response to this Item or Post. *

*/ - public static final ObjectProperty HAS_REPLY = m_model + public static final Property HAS_REPLY = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_reply"); /** @@ -279,7 +287,7 @@ public static String getURI() { * A resource that this Role applies to. *

*/ - public static final ObjectProperty HAS_SCOPE = m_model + public static final Property HAS_SCOPE = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_scope"); /** @@ -287,7 +295,7 @@ public static String getURI() { * A data Space which this resource is a part of. *

*/ - public static final ObjectProperty HAS_SPACE = m_model + public static final Property HAS_SPACE = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_space"); /** @@ -295,7 +303,7 @@ public static String getURI() { * A User who is subscribed to this Container. *

*/ - public static final ObjectProperty HAS_SUBSCRIBER = m_model + public static final Property HAS_SUBSCRIBER = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_subscriber"); /** @@ -303,7 +311,7 @@ public static String getURI() { * Points to a Usergroup that has certain access to this Space. *

*/ - public static final ObjectProperty HAS_USERGROUP = m_model + public static final Property HAS_USERGROUP = m_model .createObjectProperty("http://rdfs.org/sioc/ns#has_usergroup"); /** @@ -311,7 +319,7 @@ public static String getURI() { * A Forum that is hosted on this Site. *

*/ - public static final ObjectProperty HOST_OF = m_model + public static final Property HOST_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#host_of"); /** @@ -319,7 +327,7 @@ public static String getURI() { * Links to the latest revision of this Item or Post. *

*/ - public static final ObjectProperty LATEST_VERSION = m_model + public static final Property LATEST_VERSION = m_model .createObjectProperty("http://rdfs.org/sioc/ns#latest_version"); /** @@ -327,7 +335,7 @@ public static String getURI() { * A URI of a document which contains this SIOC object. *

*/ - public static final ObjectProperty LINK = m_model + public static final Property LINK = m_model .createObjectProperty("http://rdfs.org/sioc/ns#link"); /** @@ -335,7 +343,7 @@ public static String getURI() { * Links extracted from hyperlinks within a SIOC concept, e.g. Post or Site. *

*/ - public static final ObjectProperty LINKS_TO = m_model + public static final Property LINKS_TO = m_model .createObjectProperty("http://rdfs.org/sioc/ns#links_to"); /** @@ -343,7 +351,7 @@ public static String getURI() { * A Usergroup that this User is a member of. *

*/ - public static final ObjectProperty MEMBER_OF = m_model + public static final Property MEMBER_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#member_of"); /** @@ -351,7 +359,7 @@ public static String getURI() { * A Forum that User is a moderator of. *

*/ - public static final ObjectProperty MODERATOR_OF = m_model + public static final Property MODERATOR_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#moderator_of"); /** @@ -359,7 +367,7 @@ public static String getURI() { * An Item that this User has modified. *

*/ - public static final ObjectProperty MODIFIER_OF = m_model + public static final Property MODIFIER_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#modifier_of"); /** @@ -367,7 +375,7 @@ public static String getURI() { * Next Item or Post in a given Container sorted by date. *

*/ - public static final ObjectProperty NEXT_BY_DATE = m_model + public static final Property NEXT_BY_DATE = m_model .createObjectProperty("http://rdfs.org/sioc/ns#next_by_date"); /** @@ -375,7 +383,7 @@ public static String getURI() { * Links to the next revision of this Item or Post. *

*/ - public static final ObjectProperty NEXT_VERSION = m_model + public static final Property NEXT_VERSION = m_model .createObjectProperty("http://rdfs.org/sioc/ns#next_version"); /** @@ -383,7 +391,7 @@ public static String getURI() { * A resource owned by a particular User, for example, a weblog or image gallery. *

*/ - public static final ObjectProperty OWNER_OF = m_model + public static final Property OWNER_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#owner_of"); /** @@ -391,7 +399,7 @@ public static String getURI() { * A child Container or Forum that this Container or Forum is a parent of. *

*/ - public static final ObjectProperty PARENT_OF = m_model + public static final Property PARENT_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#parent_of"); /** @@ -399,7 +407,7 @@ public static String getURI() { * A resource that the subject is a part of. *

*/ - public static final ObjectProperty PART_OF = m_model + public static final Property PART_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#part_of"); /** @@ -407,7 +415,7 @@ public static String getURI() { * Previous Item or Post in a given Container sorted by date. *

*/ - public static final ObjectProperty PREVIOUS_BY_DATE = m_model + public static final Property PREVIOUS_BY_DATE = m_model .createObjectProperty("http://rdfs.org/sioc/ns#previous_by_date"); /** @@ -415,7 +423,7 @@ public static String getURI() { * Links to the previous revision of this Item or Post. *

*/ - public static final ObjectProperty PREVIOUS_VERSION = m_model + public static final Property PREVIOUS_VERSION = m_model .createObjectProperty("http://rdfs.org/sioc/ns#previous_version"); /** @@ -423,7 +431,7 @@ public static String getURI() { * Links either created explicitly or extracted implicitly on the HTML level from the Post. *

*/ - public static final ObjectProperty REFERENCE = m_model + public static final Property REFERENCE = m_model .createObjectProperty("http://rdfs.org/sioc/ns#reference"); /** @@ -431,7 +439,7 @@ public static String getURI() { * Related Posts for this Post, perhaps determined implicitly from topics or references. *

*/ - public static final ObjectProperty RELATED_TO = m_model + public static final Property RELATED_TO = m_model .createObjectProperty("http://rdfs.org/sioc/ns#related_to"); /** @@ -439,7 +447,7 @@ public static String getURI() { * Links to an Item or Post which this Item or Post is a reply to. *

*/ - public static final ObjectProperty REPLY_OF = m_model + public static final Property REPLY_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#reply_of"); /** @@ -447,7 +455,7 @@ public static String getURI() { * A Role that has a scope of this resource. *

*/ - public static final ObjectProperty SCOPE_OF = m_model + public static final Property SCOPE_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#scope_of"); /** @@ -455,7 +463,7 @@ public static String getURI() { * A resource which belongs to this data Space. *

*/ - public static final ObjectProperty SPACE_OF = m_model + public static final Property SPACE_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#space_of"); /** @@ -463,7 +471,7 @@ public static String getURI() { * A Container that a User is subscribed to. *

*/ - public static final ObjectProperty SUBSCRIBER_OF = m_model + public static final Property SUBSCRIBER_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#subscriber_of"); /** @@ -472,7 +480,7 @@ public static String getURI() { * SKOS category. *

*/ - public static final ObjectProperty TOPIC = m_model + public static final Property TOPIC = m_model .createObjectProperty("http://rdfs.org/sioc/ns#topic"); /** @@ -480,7 +488,7 @@ public static String getURI() { * A Space that the Usergroup has access to. *

*/ - public static final ObjectProperty USERGROUP_OF = m_model + public static final Property USERGROUP_OF = m_model .createObjectProperty("http://rdfs.org/sioc/ns#usergroup_of"); /** @@ -488,48 +496,48 @@ public static String getURI() { * The content of the Item in plain text format. *

*/ - public static final DatatypeProperty CONTENT = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#content"); + public static final Property CONTENT = m_model + .createDataProperty("http://rdfs.org/sioc/ns#content"); /** *

* The encoded content of the Post, contained in CDATA areas. *

*/ - public static final DatatypeProperty CONTENT_ENCODED = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#content_encoded"); + public static final Property CONTENT_ENCODED = m_model + .createDataProperty("http://rdfs.org/sioc/ns#content_encoded"); /** *

* When this was created, in ISO 8601 format. *

*/ - public static final DatatypeProperty CREATED_AT = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#created_at"); + public static final Property CREATED_AT = m_model + .createDataProperty("http://rdfs.org/sioc/ns#created_at"); /** *

* The content of the Post. *

*/ - public static final DatatypeProperty DESCRIPTION = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#description"); + public static final Property DESCRIPTION = m_model + .createDataProperty("http://rdfs.org/sioc/ns#description"); /** *

* An electronic mail address of the User, encoded using SHA1. *

*/ - public static final DatatypeProperty EMAIL_SHA1 = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#email_sha1"); + public static final Property EMAIL_SHA1 = m_model + .createDataProperty("http://rdfs.org/sioc/ns#email_sha1"); /** *

* First (real) name of this User. Synonyms include given name or christian name. *

*/ - public static final DatatypeProperty FIRST_NAME = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#first_name"); + public static final Property FIRST_NAME = m_model + .createDataProperty("http://rdfs.org/sioc/ns#first_name"); /** *

@@ -537,8 +545,8 @@ public static String getURI() { * of each type of SIOC concept within the same site. *

*/ - public static final DatatypeProperty ID = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#id"); + public static final Property ID = m_model + .createDataProperty("http://rdfs.org/sioc/ns#id"); /** *

@@ -546,47 +554,47 @@ public static String getURI() { * articles list the IP addresses for the creator or modifiers when the usernames are absent. *

*/ - public static final DatatypeProperty IP_ADDRESS = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#ip_address"); + public static final Property IP_ADDRESS = m_model + .createDataProperty("http://rdfs.org/sioc/ns#ip_address"); /** *

* Last (real) name of this user. Synonyms include surname or family name. *

*/ - public static final DatatypeProperty LAST_NAME = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#last_name"); + public static final Property LAST_NAME = m_model + .createDataProperty("http://rdfs.org/sioc/ns#last_name"); /** *

* When this was modified, in ISO 8601 format. *

*/ - public static final DatatypeProperty MODIFIED_AT = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#modified_at"); + public static final Property MODIFIED_AT = m_model + .createDataProperty("http://rdfs.org/sioc/ns#modified_at"); /** *

* The name of a SIOC instance, e.g. a username for a User, group name for a Usergroup, etc. *

*/ - public static final DatatypeProperty NAME = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#name"); + public static final Property NAME = m_model + .createDataProperty("http://rdfs.org/sioc/ns#name"); /** *

* A note associated with this resource, for example, if it has been edited by a User. *

*/ - public static final DatatypeProperty NOTE = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#note"); + public static final Property NOTE = m_model + .createDataProperty("http://rdfs.org/sioc/ns#note"); /** *

* The number of posts that this person has posted. *

*/ - public static final ObjectProperty NUM_POSTS = m_model + public static final Property NUM_POSTS = m_model .createObjectProperty("http://rdfs.org/sioc/ns#num_posts"); /** @@ -595,24 +603,24 @@ public static String getURI() { * structure is absent. *

*/ - public static final DatatypeProperty NUM_REPLIES = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#num_replies"); + public static final Property NUM_REPLIES = m_model + .createDataProperty("http://rdfs.org/sioc/ns#num_replies"); /** *

* The number of times this Item, Thread, User profile, etc. has been viewed. *

*/ - public static final DatatypeProperty NUM_VIEWS = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#num_views"); + public static final Property NUM_VIEWS = m_model + .createDataProperty("http://rdfs.org/sioc/ns#num_views"); /** *

* Keyword(s) describing subject of the Post. *

*/ - public static final DatatypeProperty SUBJECT = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#subject"); + public static final Property SUBJECT = m_model + .createDataProperty("http://rdfs.org/sioc/ns#subject"); /** *

@@ -620,8 +628,8 @@ public static String getURI() { * that has no parents, it would detail the topic thread. *

*/ - public static final DatatypeProperty TITLE = m_model - .createDatatypeProperty("http://rdfs.org/sioc/ns#title"); + public static final Property TITLE = m_model + .createDataProperty("http://rdfs.org/sioc/ns#title"); // Vocabulary classes // ///////////////////////// @@ -631,42 +639,42 @@ public static String getURI() { * Community is a high-level concept that defines an online community and what it consists of. *

*/ - public static final OntClass COMMUNITY = m_model.createClass("http://rdfs.org/sioc/ns#Community"); + public static final Resource COMMUNITY = m_model.createOntClass("http://rdfs.org/sioc/ns#Community"); /** *

* An area in which content Items are contained. *

*/ - public static final OntClass CONTAINER = m_model.createClass("http://rdfs.org/sioc/ns#Container"); + public static final Resource CONTAINER = m_model.createOntClass("http://rdfs.org/sioc/ns#Container"); /** *

* A discussion area on which Posts or entries are made. *

*/ - public static final OntClass FORUM = m_model.createClass("http://rdfs.org/sioc/ns#Forum"); + public static final Resource FORUM = m_model.createOntClass("http://rdfs.org/sioc/ns#Forum"); /** *

* An Item is something which can be in a Container. *

*/ - public static final OntClass ITEM = m_model.createClass("http://rdfs.org/sioc/ns#Item"); + public static final Resource ITEM = m_model.createOntClass("http://rdfs.org/sioc/ns#Item"); /** *

* An article or message that can be posted to a Forum. *

*/ - public static final OntClass POST = m_model.createClass("http://rdfs.org/sioc/ns#Post"); + public static final Resource POST = m_model.createOntClass("http://rdfs.org/sioc/ns#Post"); /** *

* A Role is a function of a User within a scope of a particular Forum, Site, etc. *

*/ - public static final OntClass ROLE = m_model.createClass("http://rdfs.org/sioc/ns#Role"); + public static final Resource ROLE = m_model.createOntClass("http://rdfs.org/sioc/ns#Role"); /** *

@@ -675,28 +683,28 @@ public static String getURI() { * Space. *

*/ - public static final OntClass SITE = m_model.createClass("http://rdfs.org/sioc/ns#Site"); + public static final Resource SITE = m_model.createOntClass("http://rdfs.org/sioc/ns#Site"); /** *

* A Space is a place where data resides, e.g. on a website, desktop, fileshare, etc. *

*/ - public static final OntClass SPACE = m_model.createClass("http://rdfs.org/sioc/ns#Space"); + public static final Resource SPACE = m_model.createOntClass("http://rdfs.org/sioc/ns#Space"); /** *

* A container for a series of threaded discussion Posts or Items. *

*/ - public static final OntClass THREAD = m_model.createClass("http://rdfs.org/sioc/ns#Thread"); + public static final Resource THREAD = m_model.createOntClass("http://rdfs.org/sioc/ns#Thread"); /** *

* A User account in an online community site. *

*/ - public static final OntClass USER_ACCOUNT = m_model.createClass("http://rdfs.org/sioc/ns#UserAccount"); + public static final Resource USER_ACCOUNT = m_model.createOntClass("http://rdfs.org/sioc/ns#UserAccount"); /** *

@@ -704,7 +712,7 @@ public static String getURI() { * control purposes. *

*/ - public static final OntClass USERGROUP = m_model.createClass("http://rdfs.org/sioc/ns#Usergroup"); + public static final Resource USERGROUP = m_model.createOntClass("http://rdfs.org/sioc/ns#Usergroup"); // Vocabulary individuals // ///////////////////////// diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/VoID.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/VoID.java index 9d31bc7a9..12d04d916 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/VoID.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/VoID.java @@ -16,12 +16,11 @@ */ package com.atomgraph.linkeddatahub.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -30,9 +29,14 @@ * @author Martynas Jusevičius {@literal } */ public class VoID { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /** The RDF model that holds the vocabulary terms */ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /** The namespace of the vocabulary as a string */ public static final String NS = "http://rdfs.org/ns/void#"; @@ -52,18 +56,18 @@ public static String getURI() public static final Resource NAMESPACE = m_model.createResource( NS ); /** Dataset class */ - public static final OntClass Dataset = m_model.createClass( NS + "Dataset" ); + public static final Resource Dataset = m_model.createOntClass( NS + "Dataset" ); /** Triples property */ - public static final DatatypeProperty triples = m_model.createDatatypeProperty( NS + "triples" ); + public static final Property triples = m_model.createDataProperty( NS + "triples" ); /** Distinct subject property */ - public static final DatatypeProperty distinctSubjects = m_model.createDatatypeProperty( NS + "distinctSubjects" ); + public static final Property distinctSubjects = m_model.createDataProperty( NS + "distinctSubjects" ); /** In dataset property */ - public static final ObjectProperty inDataset = m_model.createObjectProperty( NS + "inDataset" ); + public static final Property inDataset = m_model.createObjectProperty( NS + "inDataset" ); /** Class property */ - public static final ObjectProperty _class = m_model.createObjectProperty( NS + "class" ); + public static final Property _class = m_model.createObjectProperty( NS + "class" ); } diff --git a/src/main/java/com/atomgraph/linkeddatahub/writer/ModelXSLTWriter.java b/src/main/java/com/atomgraph/linkeddatahub/writer/ModelXSLTWriter.java index bdf1ebb5c..eb7b2daaf 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/writer/ModelXSLTWriter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/writer/ModelXSLTWriter.java @@ -15,7 +15,7 @@ */ package com.atomgraph.linkeddatahub.writer; -import com.atomgraph.client.util.DataManager; +import com.atomgraph.client.util.RDFSourceResolver; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; import com.atomgraph.linkeddatahub.model.auth.Agent; import com.atomgraph.linkeddatahub.server.io.ValidatingModelProvider; @@ -41,7 +41,6 @@ import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.XsltExecutable; import org.apache.http.HttpHeaders; -import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.rdf.model.Model; import org.apache.jena.riot.RDFFormat; import org.apache.jena.riot.RDFWriter; @@ -70,9 +69,9 @@ public class ModelXSLTWriter extends XSLTWriterBase implements MessageBodyWriter * @param dataManager RDF data manager * @param messageDigest message digest */ - public ModelXSLTWriter(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest) + public ModelXSLTWriter(XsltExecutable xsltExec, RDFSourceResolver resolver, MessageDigest messageDigest) { - super(xsltExec, ontModelSpec, dataManager, messageDigest); + super(xsltExec, resolver, messageDigest); } @Override diff --git a/src/main/java/com/atomgraph/linkeddatahub/writer/ResultSetXSLTWriter.java b/src/main/java/com/atomgraph/linkeddatahub/writer/ResultSetXSLTWriter.java index fd7b24cfe..df0d42d64 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/writer/ResultSetXSLTWriter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/writer/ResultSetXSLTWriter.java @@ -15,7 +15,7 @@ */ package com.atomgraph.linkeddatahub.writer; -import com.atomgraph.client.util.DataManager; +import com.atomgraph.client.util.RDFSourceResolver; import com.atomgraph.linkeddatahub.model.auth.Agent; import jakarta.inject.Singleton; import jakarta.ws.rs.Produces; @@ -38,7 +38,6 @@ import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.XsltExecutable; import org.apache.http.HttpHeaders; -import org.apache.jena.ontology.OntModelSpec; import org.apache.jena.query.ResultSetFormatter; import org.apache.jena.query.ResultSetRewindable; import org.slf4j.Logger; @@ -65,9 +64,9 @@ public class ResultSetXSLTWriter extends XSLTWriterBase implements MessageBodyWr * @param dataManager RDF data manager * @param messageDigest message digest */ - public ResultSetXSLTWriter(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest) + public ResultSetXSLTWriter(XsltExecutable xsltExec, RDFSourceResolver resolver, MessageDigest messageDigest) { - super(xsltExec, ontModelSpec, dataManager, messageDigest); + super(xsltExec, resolver, messageDigest); } @Override diff --git a/src/main/java/com/atomgraph/linkeddatahub/writer/XSLTWriterBase.java b/src/main/java/com/atomgraph/linkeddatahub/writer/XSLTWriterBase.java index 50fc80af8..bbdaf3442 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/writer/XSLTWriterBase.java +++ b/src/main/java/com/atomgraph/linkeddatahub/writer/XSLTWriterBase.java @@ -15,7 +15,7 @@ */ package com.atomgraph.linkeddatahub.writer; -import com.atomgraph.client.util.DataManager; +import com.atomgraph.client.util.RDFSourceResolver; import com.atomgraph.client.vocabulary.AC; import com.atomgraph.linkeddatahub.writer.factory.xslt.XsltExecutableSupplier; import com.atomgraph.linkeddatahub.model.auth.Agent; @@ -58,10 +58,10 @@ import net.sf.saxon.s9api.XdmValue; import net.sf.saxon.s9api.XsltExecutable; import org.apache.http.HttpHeaders; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntModelSpec; +import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Model; -import org.apache.jena.riot.RDFLanguages; +import org.apache.jena.riot.RDFFormat; +import org.apache.jena.riot.RDFWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,7 +88,7 @@ public abstract class XSLTWriterBase extends com.atomgraph.client.writer.XSLTWri @Inject com.atomgraph.linkeddatahub.Application system; @Inject jakarta.inject.Provider> application; - @Inject jakarta.inject.Provider dataManager; + @Inject jakarta.inject.Provider resolver; @Inject jakarta.inject.Provider xsltExecSupplier; @Inject jakarta.inject.Provider crc; @Inject jakarta.inject.Provider> authorizationContext; @@ -97,15 +97,14 @@ public abstract class XSLTWriterBase extends com.atomgraph.client.writer.XSLTWri /** * Constructs XSLT writer. - * + * * @param xsltExec compiled XSLT stylesheet - * @param ontModelSpec ontology specification - * @param dataManager RDF data manager + * @param resolver XSLT source resolver * @param messageDigest message digest */ - public XSLTWriterBase(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest) + public XSLTWriterBase(XsltExecutable xsltExec, RDFSourceResolver resolver, MessageDigest messageDigest) { - super(xsltExec, ontModelSpec, dataManager); // this DataManager will be unused as we override getDataManager() with the injected (subclassed) one + super(xsltExec, resolver); // this resolver is unused as we override getResolver() with the injected (request-scoped) one this.messageDigest = messageDigest; } @@ -189,7 +188,7 @@ public Map getParameters(MultivaluedMap headerMap, ObjectProperty property) + public URI getLinkURI(MultivaluedMap headerMap, Property property) { if (headerMap.get(jakarta.ws.rs.core.HttpHeaders.LINK) == null) return null; @@ -231,7 +230,10 @@ public StreamSource getSource(Model model) throws IOException try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { - model.write(stream, RDFLanguages.RDFXML.getName(), null); + RDFWriter.create(). + format(RDFFormat.RDFXML_PLAIN). + source(model). + output(stream); return new StreamSource(new ByteArrayInputStream(stream.toByteArray())); } } @@ -257,36 +259,30 @@ public com.atomgraph.linkeddatahub.Application getSystem() return system; } - @Override - public OntModelSpec getOntModelSpec() - { - return getSystem().getOntModelSpec(); - } - /** * Returns JAX-RS security context. - * + * * @return security context */ public SecurityContext getSecurityContext() { return securityContext; } - + @Override - public DataManager getDataManager() + public RDFSourceResolver getResolver() { - return getDataManagerProvider().get(); + return getResolverProvider().get(); } /** - * Returns a JAX-RS provider for the RDF data manager. + * Returns a JAX-RS provider for the request-scoped source resolver. * * @return provider */ - public jakarta.inject.Provider getDataManagerProvider() + public jakarta.inject.Provider getResolverProvider() { - return dataManager; + return resolver; } @Override diff --git a/src/main/java/com/atomgraph/linkeddatahub/writer/factory/DataManagerFactory.java b/src/main/java/com/atomgraph/linkeddatahub/writer/factory/SourceResolverFactory.java similarity index 70% rename from src/main/java/com/atomgraph/linkeddatahub/writer/factory/DataManagerFactory.java rename to src/main/java/com/atomgraph/linkeddatahub/writer/factory/SourceResolverFactory.java index a9774fd63..daca9faeb 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/writer/factory/DataManagerFactory.java +++ b/src/main/java/com/atomgraph/linkeddatahub/writer/factory/SourceResolverFactory.java @@ -16,18 +16,16 @@ */ package com.atomgraph.linkeddatahub.writer.factory; -import org.apache.jena.util.LocationMapper; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.ext.Provider; -import com.atomgraph.client.util.DataManager; +import com.atomgraph.client.util.RDFSourceResolver; +import com.atomgraph.client.util.jena.PrefixGraphRepository; import com.atomgraph.linkeddatahub.apps.model.Application; import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; import com.atomgraph.linkeddatahub.client.GraphStoreClient; import com.atomgraph.linkeddatahub.server.security.AgentContext; import com.atomgraph.linkeddatahub.vocabulary.LAPP; -import com.atomgraph.linkeddatahub.writer.impl.DataManagerImpl; -import java.net.URI; -import java.util.HashMap; +import com.atomgraph.linkeddatahub.writer.impl.SameSiteSourceResolver; import java.util.Optional; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; @@ -40,15 +38,15 @@ import org.slf4j.LoggerFactory; /** - * JAX-RS provider for DataManager. - * + * JAX-RS provider for the request-scoped XSLT source resolver. + * * @author Martynas Jusevičius {@literal } - * @see com.atomgraph.client.util.DataManager + * @see com.atomgraph.client.util.RDFSourceResolver */ @Provider -public class DataManagerFactory implements Factory +public class SourceResolverFactory implements Factory { - private static final Logger log = LoggerFactory.getLogger(DataManagerFactory.class); + private static final Logger log = LoggerFactory.getLogger(SourceResolverFactory.class); @Context UriInfo uriInfo; @Context HttpServletRequest httpServletRequest; @@ -58,40 +56,37 @@ public class DataManagerFactory implements Factory @Inject com.atomgraph.linkeddatahub.Application system; @Override - public DataManager provide() + public RDFSourceResolver provide() { - // Always return DataManager, falling back to system DataManager if no Application (e.g., for error responses) - return getDataManager(getApplication()); + // falls back to the global repository if there is no application (e.g. for error responses) + return getResolver(getApplication()); } @Override - public void dispose(DataManager t) + public void dispose(RDFSourceResolver t) { } /** - * Returns RDF data manager. + * Returns the request-scoped source resolver, backed by the application's ontology repository + * (or the global repository) and a delegating Graph Store client. * - * @param appOpt optional end-user application (if empty, system DataManager is used) - * @return data manager + * @param appOpt optional end-user application (if empty, the global repository is used) + * @return source resolver */ - public DataManager getDataManager(Optional appOpt) + public RDFSourceResolver getResolver(Optional appOpt) { - final com.atomgraph.core.util.jena.DataManager baseManager; + final PrefixGraphRepository repository; if (appOpt.isPresent() && appOpt.get().canAs(EndUserApplication.class)) - baseManager = (com.atomgraph.core.util.jena.DataManager)getSystem().getOntModelSpec(appOpt.get().as(EndUserApplication.class)).getDocumentManager().getFileManager(); + repository = getSystem().getRepository(appOpt.get().as(EndUserApplication.class)); else - baseManager = getSystem().getDataManager(); + repository = getSystem().getRepository(); GraphStoreClient gsc = GraphStoreClient.create(getSystem().getClient(), getSystem().getMediaTypes()). delegation(getUriInfo().getBaseUri(), getAgentContext()); - // copy cached models over from the app's FileManager - return new DataManagerImpl(LocationMapper.get(), new HashMap<>(baseManager.getModelCache()), - gsc, true, getSystem().isPreemptiveAuth(), getSystem().isResolvingUncached(), - getSystem().getBaseURI(), - getAgentContext()); + return new SameSiteSourceResolver(repository, gsc, getSystem().isResolvingUncached(), getSystem().getBaseURI()); } /** diff --git a/src/main/java/com/atomgraph/linkeddatahub/writer/impl/DataManagerImpl.java b/src/main/java/com/atomgraph/linkeddatahub/writer/impl/SameSiteSourceResolver.java similarity index 59% rename from src/main/java/com/atomgraph/linkeddatahub/writer/impl/DataManagerImpl.java rename to src/main/java/com/atomgraph/linkeddatahub/writer/impl/SameSiteSourceResolver.java index b62cfefad..72134d5af 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/writer/impl/DataManagerImpl.java +++ b/src/main/java/com/atomgraph/linkeddatahub/writer/impl/SameSiteSourceResolver.java @@ -16,67 +16,57 @@ */ package com.atomgraph.linkeddatahub.writer.impl; -import org.apache.jena.util.LocationMapper; -import java.net.URI; +import com.atomgraph.client.util.RDFSourceResolver; import com.atomgraph.core.client.GraphStoreClient; -import com.atomgraph.linkeddatahub.server.security.AgentContext; +import com.atomgraph.client.util.jena.PrefixGraphRepository; import com.google.common.net.InternetDomainName; -import java.util.Map; -import org.apache.jena.rdf.model.Model; +import java.net.URI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Manager for remote RDF dataset access. - * Documents can be mapped to local copies. - * + * XSLT source resolver that restricts dereferencing of uncached URIs to the same site as the + * application. Mapped (bundled) and same-site URIs resolve; arbitrary external URIs do not. + * Backed by a {@link PrefixGraphRepository}; authenticated delegation is handled by the supplied + * {@link GraphStoreClient}. + * * @author Martynas Jusevičius {@literal } */ -public class DataManagerImpl extends com.atomgraph.client.util.DataManagerImpl +public class SameSiteSourceResolver extends RDFSourceResolver { - private static final Logger log = LoggerFactory.getLogger(DataManagerImpl.class); - + private static final Logger log = LoggerFactory.getLogger(SameSiteSourceResolver.class); + private final URI rootContextURI; - private final AgentContext agentContext; /** - * Constructs RDF data manager. - * - * @param mapper location mapper - * @param modelCache model cache - * @param gsc Graph Store client - * @param cacheModelLoads true if loaded RDF models are cached - * @param preemptiveAuth true if HTTP basic auth is sent preemptively + * Constructs the resolver. + * + * @param repository graph repository (bundled/cached graphs + URI→location mapping) + * @param gsc Graph Store client (with delegation) * @param resolvingUncached true if uncached URLs are resolved * @param rootContextURI the root URI of the JAX-RS application - * @param agentContext agent context */ - public DataManagerImpl(LocationMapper mapper, Map modelCache, - GraphStoreClient gsc, - boolean cacheModelLoads, boolean preemptiveAuth, boolean resolvingUncached, - URI rootContextURI, - AgentContext agentContext) + public SameSiteSourceResolver(PrefixGraphRepository repository, GraphStoreClient gsc, boolean resolvingUncached, URI rootContextURI) { - super(mapper, modelCache, gsc, cacheModelLoads, preemptiveAuth, resolvingUncached); + super(repository, gsc, resolvingUncached); this.rootContextURI = rootContextURI; - this.agentContext = agentContext; } - + @Override - public boolean resolvingUncached(String filenameOrURI) + protected boolean resolvingUncached(String filenameOrURI) { - if (super.resolvingUncached(filenameOrURI) && !isMapped(filenameOrURI)) + if (super.resolvingUncached(filenameOrURI) && !getRepository().isMapped(filenameOrURI)) { // Allow resolving URIs from the same site (e.g., localhost:4443/static/..., admin.localhost:4443/ns) return isSameSite(getRootContextURI(), URI.create(filenameOrURI)); } - return false; // super.resolvingUncached(filenameOrURI); // configured in web.xml + return false; } /** * Checks if two URIs are from the same site (schemeful same-site). - * This allows subdomains like admin.localhost and localhost to be considered part of the same LinkedDataHub instance. + * Allows subdomains like admin.localhost and localhost to be considered part of the same instance. * Ports are ignored per the same-site definition. * * @param uri1 first URI @@ -99,12 +89,9 @@ private boolean isSameSite(URI uri1, URI uri2) InternetDomainName domain1 = InternetDomainName.from(host1); InternetDomainName domain2 = InternetDomainName.from(host2); - // For localhost domains, compare the full host (localhost == localhost, admin.localhost != localhost at domain level) - // But we want to treat them as same root domain, so just check if both end with "localhost" if (host1.equals("localhost") || host1.endsWith(".localhost")) return host2.equals("localhost") || host2.endsWith(".localhost"); - // For regular domains, compare top private domains if (domain1.isTopPrivateDomain() && domain2.isTopPrivateDomain()) return domain1.equals(domain2); if (domain1.hasPublicSuffix() && domain2.hasPublicSuffix()) @@ -114,7 +101,6 @@ private boolean isSameSite(URI uri1, URI uri2) } catch (IllegalArgumentException ex) { - // Invalid domain name, fall back to simple equality check if (log.isDebugEnabled()) log.debug("Could not parse domain names for comparison: {} and {}", host1, host2); return false; } @@ -122,7 +108,7 @@ private boolean isSameSite(URI uri1, URI uri2) /** * Returns the root URI of the JAX-RS application. - * + * * @return root URI */ public URI getRootContextURI() @@ -130,14 +116,4 @@ public URI getRootContextURI() return rootContextURI; } - /** - * Returns the agent context. - * - * @return agent context or null - */ - public AgentContext getAgentContext() - { - return agentContext; - } - -} \ No newline at end of file +} diff --git a/src/main/java/com/atomgraph/server/io/ValidatingDatasetProvider.java b/src/main/java/com/atomgraph/server/io/ValidatingDatasetProvider.java index 500fb2e5a..341e7c7eb 100644 --- a/src/main/java/com/atomgraph/server/io/ValidatingDatasetProvider.java +++ b/src/main/java/com/atomgraph/server/io/ValidatingDatasetProvider.java @@ -30,7 +30,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.Providers; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.query.Dataset; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +52,7 @@ public class ValidatingDatasetProvider extends DatasetProvider @Context private Providers providers; - @Inject jakarta.inject.Provider> ontology; + @Inject jakarta.inject.Provider> ontology; @Override public Dataset readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException @@ -70,7 +70,7 @@ public Dataset validate(Dataset dataset) if (getOntology().isPresent()) { // SPIN validation - Validator validator = new Validator(getOntology().get().getOntModel()); + Validator validator = new Validator(getOntology().get()); List cvs = validator.validate(dataset.getDefaultModel()); if (!cvs.isEmpty()) { @@ -79,7 +79,7 @@ public Dataset validate(Dataset dataset) } // SHACL validation - Shapes shapes = Shapes.parse(getOntology().get().getOntModel().getGraph()); + Shapes shapes = Shapes.parse(getOntology().get().getGraph()); ValidationReport report = ShaclValidator.get().validate(shapes, dataset.getDefaultModel().getGraph()); if (!report.conforms()) { @@ -113,7 +113,7 @@ public Dataset validate(Dataset dataset) return dataset; } - public Optional getOntology() + public Optional getOntology() { return ontology.get(); } diff --git a/src/main/java/com/atomgraph/server/io/ValidatingModelProvider.java b/src/main/java/com/atomgraph/server/io/ValidatingModelProvider.java index 416d8ada1..1b348127b 100644 --- a/src/main/java/com/atomgraph/server/io/ValidatingModelProvider.java +++ b/src/main/java/com/atomgraph/server/io/ValidatingModelProvider.java @@ -16,7 +16,7 @@ package com.atomgraph.server.io; -import org.apache.jena.ontology.Ontology; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.rdf.model.Model; import java.io.IOException; import java.io.InputStream; @@ -51,7 +51,7 @@ public class ValidatingModelProvider extends BasedModelProvider @Context private Providers providers; - @Inject jakarta.inject.Provider> ontology; + @Inject jakarta.inject.Provider> ontology; @Override public Model readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException @@ -69,7 +69,7 @@ public Model validate(Model model) if (getOntology().isPresent()) { // SPIN validation - List cvs = new Validator(getOntology().get().getOntModel()).validate(model); + List cvs = new Validator(getOntology().get()).validate(model); if (!cvs.isEmpty()) { if (log.isDebugEnabled()) log.debug("SPIN constraint violations: {}", cvs); @@ -77,7 +77,7 @@ public Model validate(Model model) } // SHACL validation - Shapes shapes = Shapes.parse(getOntology().get().getOntModel().getGraph()); + Shapes shapes = Shapes.parse(getOntology().get().getGraph()); ValidationReport report = ShaclValidator.get().validate(shapes, model.getGraph()); if (!report.conforms()) { @@ -100,7 +100,7 @@ public Model processWrite(Model model) return model; } - public Optional getOntology() + public Optional getOntology() { return ontology.get(); } diff --git a/src/main/java/com/atomgraph/server/util/Validator.java b/src/main/java/com/atomgraph/server/util/Validator.java index fc66576e5..67a6afe98 100644 --- a/src/main/java/com/atomgraph/server/util/Validator.java +++ b/src/main/java/com/atomgraph/server/util/Validator.java @@ -16,7 +16,7 @@ package com.atomgraph.server.util; -import org.apache.jena.ontology.OntModel; +import org.apache.jena.ontapi.model.OntModel; import org.apache.jena.rdf.model.Model; import java.util.List; import org.slf4j.Logger; diff --git a/src/main/java/com/atomgraph/server/vocabulary/HTTP.java b/src/main/java/com/atomgraph/server/vocabulary/HTTP.java index e9316e1ee..316d6e3b9 100644 --- a/src/main/java/com/atomgraph/server/vocabulary/HTTP.java +++ b/src/main/java/com/atomgraph/server/vocabulary/HTTP.java @@ -16,12 +16,11 @@ package com.atomgraph.server.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -33,8 +32,13 @@ public class HTTP { + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } + /**

The RDF model that holds the vocabulary terms

*/ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /**

The namespace of the vocabulary as a string

*/ public static final String NS = "http://www.w3.org/2011/http#"; @@ -49,16 +53,16 @@ public static String getURI() /**

The namespace of the vocabulary as a resource

*/ public static final Resource NAMESPACE = m_model.createResource( NS ); - public static final OntClass Response = m_model.createClass( NS + "Response" ); + public static final Resource Response = m_model.createOntClass( NS + "Response" ); - public static final ObjectProperty sc = m_model.createObjectProperty( NS + "sc" ); + public static final Property sc = m_model.createObjectProperty( NS + "sc" ); - public static final DatatypeProperty statusCodeValue = m_model.createDatatypeProperty( NS + "statusCodeValue" ); + public static final Property statusCodeValue = m_model.createDataProperty( NS + "statusCodeValue" ); - public static final DatatypeProperty reasonPhrase = m_model.createDatatypeProperty( NS + "reasonPhrase" ); + public static final Property reasonPhrase = m_model.createDataProperty( NS + "reasonPhrase" ); - public static final DatatypeProperty absoluteURI = m_model.createDatatypeProperty( NS + "absoluteURI" ); + public static final Property absoluteURI = m_model.createDataProperty( NS + "absoluteURI" ); - public static final DatatypeProperty absolutePath = m_model.createDatatypeProperty( NS + "absolutePath" ); + public static final Property absolutePath = m_model.createDataProperty( NS + "absolutePath" ); } diff --git a/src/main/java/com/atomgraph/server/vocabulary/LDT.java b/src/main/java/com/atomgraph/server/vocabulary/LDT.java index 5b59ee7f8..28cb52bcf 100644 --- a/src/main/java/com/atomgraph/server/vocabulary/LDT.java +++ b/src/main/java/com/atomgraph/server/vocabulary/LDT.java @@ -16,12 +16,11 @@ */ package com.atomgraph.server.vocabulary; -import org.apache.jena.ontology.DatatypeProperty; -import org.apache.jena.ontology.ObjectProperty; -import org.apache.jena.ontology.OntClass; -import org.apache.jena.ontology.OntModel; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Property; + import org.apache.jena.rdf.model.Resource; /** @@ -31,8 +30,13 @@ */ public final class LDT { + + static + { + org.apache.jena.sys.JenaSystem.init(); // ensure Jena (RDFS vocab) is initialized before ontapi touches it + } /**

The RDF model that holds the vocabulary terms

*/ - private static OntModel m_model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); + private static OntModel m_model = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); /**

The namespace of the vocabulary as a string

*/ public static final String NS = "https://www.w3.org/ns/ldt#"; @@ -47,51 +51,51 @@ public static String getURI() /**

The namespace of the vocabulary as a resource

*/ public static final Resource NAMESPACE = m_model.createResource( NS ); - public static final OntClass Application = m_model.createClass( NS + "Application" ); + public static final Resource Application = m_model.createOntClass( NS + "Application" ); - public static final OntClass Ontology = m_model.createClass( NS + "Ontology" ); + public static final Resource Ontology = m_model.createOntClass( NS + "Ontology" ); - public static final OntClass Template = m_model.createClass( NS + "Template" ); + public static final Resource Template = m_model.createOntClass( NS + "Template" ); - public static final OntClass Parameter = m_model.createClass( NS + "Parameter" ); + public static final Resource Parameter = m_model.createOntClass( NS + "Parameter" ); - public static final OntClass TemplateCall = m_model.createClass( NS + "TemplateCall" ); + public static final Resource TemplateCall = m_model.createOntClass( NS + "TemplateCall" ); - public static final OntClass Argument = m_model.createClass( NS + "Argument" ); + public static final Resource Argument = m_model.createOntClass( NS + "Argument" ); - public static final ObjectProperty base = m_model.createObjectProperty( NS + "base" ); + public static final Property base = m_model.createObjectProperty( NS + "base" ); - public static final ObjectProperty ontology = m_model.createObjectProperty( NS + "ontology" ); + public static final Property ontology = m_model.createObjectProperty( NS + "ontology" ); - public static final ObjectProperty service = m_model.createObjectProperty( NS + "service" ); + public static final Property service = m_model.createObjectProperty( NS + "service" ); - public static final ObjectProperty arg = m_model.createObjectProperty( NS + "arg" ); + public static final Property arg = m_model.createObjectProperty( NS + "arg" ); - public static final DatatypeProperty paramName = m_model.createDatatypeProperty( NS + "paramName" ); + public static final Property paramName = m_model.createDataProperty( NS + "paramName" ); // "extends" is a reserved keyword in Java, obviously - public static final ObjectProperty extends_ = m_model.createObjectProperty( NS + "extends" ); + public static final Property extends_ = m_model.createObjectProperty( NS + "extends" ); - public static final DatatypeProperty path = m_model.createDatatypeProperty( NS + "path" ); + public static final Property path = m_model.createDataProperty( NS + "path" ); - public static final ObjectProperty query = m_model.createObjectProperty( NS + "query" ); + public static final Property query = m_model.createObjectProperty( NS + "query" ); - public static final ObjectProperty update = m_model.createObjectProperty( NS + "update" ); + public static final Property update = m_model.createObjectProperty( NS + "update" ); - public static final DatatypeProperty match = m_model.createDatatypeProperty( NS + "match" ); + public static final Property match = m_model.createDataProperty( NS + "match" ); - public static final DatatypeProperty priority = m_model.createDatatypeProperty( NS + "priority" ); + public static final Property priority = m_model.createDataProperty( NS + "priority" ); - public static final DatatypeProperty fragment = m_model.createDatatypeProperty( NS + "fragment" ); + public static final Property fragment = m_model.createDataProperty( NS + "fragment" ); - public static final ObjectProperty param = m_model.createObjectProperty( NS + "param" ); + public static final Property param = m_model.createObjectProperty( NS + "param" ); - public static final ObjectProperty loadClass = m_model.createObjectProperty( NS + "loadClass" ); + public static final Property loadClass = m_model.createObjectProperty( NS + "loadClass" ); - public static final DatatypeProperty cacheControl = m_model.createDatatypeProperty( NS + "cacheControl" ); + public static final Property cacheControl = m_model.createDataProperty( NS + "cacheControl" ); - public static final ObjectProperty lang = m_model.createObjectProperty( NS + "lang" ); + public static final Property lang = m_model.createObjectProperty( NS + "lang" ); - public static final ObjectProperty template = m_model.createObjectProperty( NS + "template" ); + public static final Property template = m_model.createObjectProperty( NS + "template" ); } diff --git a/src/main/resources/com/atomgraph/linkeddatahub/app/admin/lacl.ttl b/src/main/resources/com/atomgraph/linkeddatahub/app/admin/lacl.ttl index ae9068ccf..a45a58977 100644 --- a/src/main/resources/com/atomgraph/linkeddatahub/app/admin/lacl.ttl +++ b/src/main/resources/com/atomgraph/linkeddatahub/app/admin/lacl.ttl @@ -69,7 +69,7 @@ # authorization requests -:AuthorizationRequest a rdfs:Class ; +:AuthorizationRequest a rdfs:Class, owl:Class ; spin:constructor :AuthorizationRequestConstructor ; spin:constraint :MissingRequestMode, :MissingRequestAgent, :MissingRequestAccessTo ; rdfs:label "Authorization request" ; @@ -108,7 +108,7 @@ WHERE {}""" ; rdfs:isDefinedBy : . -:CreatorAuthorization a rdfs:Class ; +:CreatorAuthorization a rdfs:Class, owl:Class ; rdfs:subClassOf acl:Authorization ; rdfs:label "Creator access" ; rdfs:comment "Creators have full control of their created resources" ; diff --git a/src/main/resources/com/atomgraph/linkeddatahub/dh.ttl b/src/main/resources/com/atomgraph/linkeddatahub/dh.ttl index 2c8045594..a3dae50f2 100644 --- a/src/main/resources/com/atomgraph/linkeddatahub/dh.ttl +++ b/src/main/resources/com/atomgraph/linkeddatahub/dh.ttl @@ -34,21 +34,21 @@ # CLASSES -:Document a rdfs:Class ; +:Document a rdfs:Class, owl:Class ; rdfs:subClassOf foaf:Document ; # ldt:path "{slug}/" ; rdfs:label "Document" ; # spin:constructor :DocumentConstructor ; rdfs:isDefinedBy : . -:Container a rdfs:Class ; +:Container a rdfs:Class, owl:Class ; rdfs:subClassOf :Document, sioc:Container ; # spin:constructor :ContainerConstructor ; rdfs:label "Container" ; rdfs:comment "An area in which content Items are contained" ; rdfs:isDefinedBy : . -:Item a rdfs:Class ; +:Item a rdfs:Class, owl:Class ; rdfs:subClassOf :Document, sioc:Item ; # spin:constructor :ItemConstructor ; rdfs:label "Item" ; diff --git a/src/main/resources/com/atomgraph/linkeddatahub/ldt.ttl b/src/main/resources/com/atomgraph/linkeddatahub/ldt.ttl index 5c847a307..120859f87 100644 --- a/src/main/resources/com/atomgraph/linkeddatahub/ldt.ttl +++ b/src/main/resources/com/atomgraph/linkeddatahub/ldt.ttl @@ -155,29 +155,29 @@ # CLASSES -:InheritedProperty a rdfs:Class ; +:InheritedProperty a rdfs:Class, owl:Class ; rdfs:subClassOf owl:AnnotationProperty ; rdfs:label "Inherited property" ; rdfs:comment "Values of this property are inherited by subclasses that do not have this property" ; rdfs:isDefinedBy : . -:Application a rdfs:Class ; +:Application a rdfs:Class, owl:Class ; rdfs:label "Application" ; rdfs:comment "Declarative Linked Data application which structure is defined by its ontology and data is access via its SPARQL service" ; rdfs:isDefinedBy : . -:Ontology a rdfs:Class ; +:Ontology a rdfs:Class, owl:Class ; rdfs:subClassOf owl:Ontology ; rdfs:label "Ontology" ; rdfs:comment "Ontology annotated with Linked Data Templates" ; rdfs:isDefinedBy : . -:Template a rdfs:Class ; +:Template a rdfs:Class, owl:Class ; rdfs:label "Resource template" ; rdfs:comment "A class of RDF resources that share the same URI template and SPARQL query template" ; rdfs:isDefinedBy : . -:Query a rdfs:Class ; +:Query a rdfs:Class, owl:Class ; rdfs:subClassOf sp:Query, [ a owl:Class ; owl:unionOf (sp:Describe sp:Construct) ; rdfs:label "Graph query forms" @@ -185,12 +185,12 @@ rdfs:label "Query" ; rdfs:isDefinedBy : . -:Update a rdfs:Class ; +:Update a rdfs:Class, owl:Class ; rdfs:subClassOf sp:Update ; rdfs:label "Update" ; rdfs:isDefinedBy : . -:Parameter a rdfs:Class ; +:Parameter a rdfs:Class, owl:Class ; rdfs:subClassOf spl:Argument ; rdfs:label "Parameter" ; rdfs:comment "Represents a query parameter that has predicate, value type, default value etc." ; diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilterTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilterTest.java new file mode 100644 index 000000000..9478f75d2 --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyFilterTest.java @@ -0,0 +1,68 @@ +/** + * Copyright 2026 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.server.filter.request; + +import com.atomgraph.client.util.jena.PrefixGraphRepository; +import org.apache.jena.graph.Graph; +import org.apache.jena.rdf.model.ModelFactory; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Characterization tests for {@link OntologyFilter#addDocumentModel}, which caches an imported graph + * under a SECONDARY key: the fragment-stripped document URI. Pins the dual-key caching behavior + * retained from the legacy implementation. + * + * @author Martynas Jusevičius {@literal } + */ +public class OntologyFilterTest +{ + + /** An import URI with a fragment is also cached under its fragment-stripped document URI. */ + @Test + public void testAddDocumentModelCachesUnderStrippedDocumentURI() + { + PrefixGraphRepository repository = new PrefixGraphRepository(null); + String importURI = "http://example.org/onto#"; + String docURI = "http://example.org/onto"; + + Graph imported = ModelFactory.createDefaultModel().getGraph(); + repository.put(importURI, imported); + + OntologyFilter.addDocumentModel(repository, importURI); + + assertTrue(repository.isCached(docURI), "import graph should be cached under the document URI"); + assertSame(imported, repository.get(docURI)); + } + + /** If the document URI is mapped to a different location, the secondary cache write is skipped. */ + @Test + public void testAddDocumentModelSkipsWhenDocumentURIMapped() + { + PrefixGraphRepository repository = new PrefixGraphRepository(null); + String importURI = "http://example.org/mapped#"; + String docURI = "http://example.org/mapped"; + + repository.addLocationMapping(docURI, "file:elsewhere.ttl"); // resolve(docURI) != docURI -> skip + repository.put(importURI, ModelFactory.createDefaultModel().getGraph()); + + OntologyFilter.addDocumentModel(repository, importURI); + + assertFalse(repository.isCached(docURI), "mapped document URI should not be cached as a secondary key"); + } + +} diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyImportsCharacterizationTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyImportsCharacterizationTest.java new file mode 100644 index 000000000..312a61415 --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/server/filter/request/OntologyImportsCharacterizationTest.java @@ -0,0 +1,91 @@ +/** + * Copyright 2026 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.server.filter.request; + +import com.atomgraph.client.util.jena.PrefixGraphRepository; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; +import org.apache.jena.vocabulary.OWL; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Pins {@link OntologyFilter#loadOntology}: it flattens the owl:imports closure into one graph, + * applies RDFS inference, and materializes the inferences into the repository cache — without ontapi + * managing a union-graph hierarchy over the shared repository (which collides on duplicate ontology IDs). + * + * @author Martynas Jusevičius {@literal } + */ +public class OntologyImportsCharacterizationTest +{ + + private static final String BASE_URI = "http://example.org/base"; + private static final String IMPORT_URI = "http://example.org/imported"; + private static final String NS = "http://example.org/ns#"; + + @Test + public void testLoadOntologyFlattensClosureWithMaterializedRDFSInference() + { + PrefixGraphRepository repository = new PrefixGraphRepository(null); + + Resource a = ResourceFactory.createResource(NS + "A"); + Resource b = ResourceFactory.createResource(NS + "B"); + Resource x = ResourceFactory.createResource(NS + "x"); + + // imported ontology: A declared as owl:Class only; B declared as rdfs:Class only (mimicking third-party vocabs like sp.ttl); + // B rdfs:subClassOf A; individual x a B + Model imported = ModelFactory.createDefaultModel(); + imported.add(imported.createResource(IMPORT_URI), RDF.type, OWL.Ontology); + imported.add(a, RDF.type, OWL.Class); + imported.add(b, RDF.type, RDFS.Class); + imported.add(b, RDFS.subClassOf, a); + imported.add(x, RDF.type, b); + repository.put(IMPORT_URI, imported.getGraph()); + + // base ontology owl:imports the imported one + Model base = ModelFactory.createDefaultModel(); + Resource baseOnt = base.createResource(BASE_URI); + base.add(baseOnt, RDF.type, OWL.Ontology); + base.add(baseOnt, OWL.imports, base.createResource(IMPORT_URI)); + repository.put(BASE_URI, base.getGraph()); + + OntologyFilter.loadOntology(repository, BASE_URI); + + Model result = ModelFactory.createModelForGraph(repository.get(BASE_URI)); + // (a) imported terms flattened into the cached graph + assertTrue(result.contains(b, RDFS.subClassOf, a), "imported terms should be flattened in"); + // (b) RDFS inference materialized as a concrete triple: x a A + assertTrue(result.contains(x, RDF.type, a), "RDFS-inferred 'x a A' should be materialized in the cached graph"); + // (c) the import is also cached under its (fragment-stripped) document URI + assertTrue(repository.isCached(IMPORT_URI), "import should remain cached"); + // (d) REGRESSION GUARD: both owl:Class and rdfs:Class-only terms must be recognized as OntClasses by the returned + // model, so GET /ns?forClass= resolves the class and runs its SPIN constructor. + // OntologyFilter promotes all rdfs:Class subjects to owl:Class so OWL2 profiles (which do not recognize bare + // rdfs:Class) can find third-party vocab terms like sp:Describe. + OntModel ontology = OntModelFactory.createModel(repository.get(BASE_URI), OntSpecification.OWL2_FULL_MEM); + assertNotNull(ontology.getOntClass(NS + "A"), "owl:Class term must be recognized as an OntClass under OWL2_FULL_MEM"); + assertNotNull(ontology.getOntClass(NS + "B"), "rdfs:Class-only term must be recognized as an OntClass after promotion"); + } + +} diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/util/OntologyRepositoryTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/util/OntologyRepositoryTest.java new file mode 100644 index 000000000..33b3183f2 --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/server/util/OntologyRepositoryTest.java @@ -0,0 +1,111 @@ +/** + * Copyright 2026 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.server.util; + +import com.atomgraph.core.client.GraphStoreClient; +import com.atomgraph.linkeddatahub.apps.model.AdminApplication; +import com.atomgraph.linkeddatahub.apps.model.EndUserApplication; +import com.atomgraph.linkeddatahub.model.Service; +import com.atomgraph.linkeddatahub.model.ServiceContext; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.NodeFactory; +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.vocabulary.OWL; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Characterization tests for {@link OntologyRepository}, which resolves an ontology graph + * SPARQL-first (admin endpoint CONSTRUCT) and falls back to the superclass (bundled mapping / HTTP) + * only when the SPARQL result is empty. + * + * @author Martynas Jusevičius {@literal } + */ +@ExtendWith(MockitoExtension.class) +public class OntologyRepositoryTest +{ + + private static final String ONTOLOGY_URI = "http://example.org/ontology#"; + private static final Query ONTOLOGY_QUERY = QueryFactory.create("CONSTRUCT { ?ontology ?p ?o } WHERE { ?ontology ?p ?o }"); + + @Mock com.atomgraph.linkeddatahub.Application system; + @Mock EndUserApplication app; + @Mock AdminApplication adminApp; + @Mock Service service; + @Mock ServiceContext serviceContext; + @Mock com.atomgraph.core.client.SPARQLClient sparqlClient; + @Mock Response response; + @Mock GraphStoreClient gsc; + + private void stubSPARQLChain(Model sparqlResult) + { + when(app.getAdminApplication()).thenReturn(adminApp); + when(adminApp.getService()).thenReturn(service); + when(system.getServiceContext(service)).thenReturn(serviceContext); + when(serviceContext.getSPARQLClient()).thenReturn(sparqlClient); + when(sparqlClient.query(any(Query.class), eq(Model.class), any(MultivaluedMap.class), any(MultivaluedMap.class))).thenReturn(response); + when(response.readEntity(Model.class)).thenReturn(sparqlResult); + } + + /** A non-empty admin SPARQL CONSTRUCT result is returned directly; the HTTP fallback is not used. */ + @Test + public void testSPARQLResultUsedWhenNonEmpty() + { + Model sparqlResult = ModelFactory.createDefaultModel(); + sparqlResult.createResource(ONTOLOGY_URI).addProperty(RDF.type, OWL.Ontology); + stubSPARQLChain(sparqlResult); + + OntologyRepository repository = new OntologyRepository(app, system, gsc, ONTOLOGY_QUERY); + Graph result = repository.get(ONTOLOGY_URI); + + assertTrue(result.contains(NodeFactory.createURI(ONTOLOGY_URI), RDF.type.asNode(), OWL.Ontology.asNode())); + verify(gsc, never()).getModel(any()); + } + + /** An empty SPARQL result falls back to the Graph Store client (HTTP) load. */ + @Test + public void testFallsBackToHttpWhenSPARQLEmpty() + { + Model empty = ModelFactory.createDefaultModel(); + stubSPARQLChain(empty); + + Model fallback = ModelFactory.createDefaultModel(); + fallback.createResource(ONTOLOGY_URI).addProperty(RDFS.label, "fallback"); + when(gsc.getModel(ONTOLOGY_URI)).thenReturn(fallback); + + OntologyRepository repository = new OntologyRepository(app, system, gsc, ONTOLOGY_QUERY); + Graph result = repository.get(ONTOLOGY_URI); + + assertTrue(result.contains(NodeFactory.createURI(ONTOLOGY_URI), RDFS.label.asNode(), NodeFactory.createLiteralString("fallback"))); + } + +} diff --git a/src/test/java/com/atomgraph/linkeddatahub/server/util/SPINConstraintValidationTest.java b/src/test/java/com/atomgraph/linkeddatahub/server/util/SPINConstraintValidationTest.java new file mode 100644 index 000000000..6d2062f9c --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/server/util/SPINConstraintValidationTest.java @@ -0,0 +1,98 @@ +/** + * Copyright 2026 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.server.util; + +import com.atomgraph.server.util.Validator; +import com.atomgraph.spinrdf.constraints.ConstraintViolation; +import com.atomgraph.spinrdf.constraints.SPINConstraints; +import java.util.List; +import org.apache.jena.ontapi.OntModelFactory; +import org.apache.jena.ontapi.OntSpecification; +import org.apache.jena.ontapi.model.OntModel; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.vocabulary.RDF; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Regression guard for SPIN constraint validation under the ontapi migration. + * + * {@code SPINConstraints} resolves a constraint through Jena enhanced-node polymorphism + * ({@code canAs(Query)}/{@code canAs(TemplateCall)}), which an ontapi {@link OntModel}'s profile-aware personality + * does not carry — so before the fix, validation silently fired nothing. twirl now re-bases the constraint model + * onto its SPIN personality internally (twirl {@code SPINConstraints.check}), so {@link Validator} can hand it the + * ontapi model directly. This pins that the real {@code dh:Item spin:constraint :MissingTitle} template constraint + * flags a {@code dh:Item} that lacks {@code dct:title}. + * + * @author Martynas Jusevičius {@literal } + */ +public class SPINConstraintValidationTest +{ + + static + { + org.apache.jena.sys.JenaSystem.init(); + } + + private static final String DH = "https://www.w3.org/ns/ldt/document-hierarchy#"; + + /** The full owl:imports closure the ontology pipeline assembles for the document-hierarchy ontology. */ + private OntModel loadOntology() + { + Model closure = ModelFactory.createDefaultModel(); + for (String classpath : new String[] { + "etc/sp.ttl", "etc/spl.spin.ttl", "etc/spin.ttl", + "com/atomgraph/linkeddatahub/dh.ttl", + "com/atomgraph/linkeddatahub/def.ttl", + "com/atomgraph/linkeddatahub/ldh.ttl" }) + RDFDataMgr.read(closure, classpath); + + // mirror OntologyFilter.loadOntology: RDFS-infer then materialize into a plain OWL2_FULL_MEM graph + OntModel inferred = OntModelFactory.createModel(closure.getGraph(), OntSpecification.OWL2_FULL_MEM_RDFS_INF); + OntModel materialized = OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM); + materialized.add(inferred); + return materialized; + } + + /** A dh:Item with NO dct:title — violates the MissingTitle constraint. */ + private Model violatingItem() + { + Model data = ModelFactory.createDefaultModel(); + data.createResource("http://example.org/doc").addProperty(RDF.type, data.createResource(DH + "Item")); + return data; + } + + @Test + public void testMissingTitleConstraintFiresThroughValidator() + { + List violations = new Validator(loadOntology()).validate(violatingItem()); + assertFalse(violations.isEmpty(), "MissingTitle SPIN constraint must fire on a dh:Item without dct:title"); + } + + @Test + public void testRawOntApiModelFiresViaTwirlRebase() + { + // The ontapi OntModel's profile-aware personality lacks the SPIN views, so canAs(...) is false on it. + // twirl's SPINConstraints.check re-bases the constraint model onto its own SPIN personality, so passing the + // ontapi model straight in still fires. Guards against twirl regressing that re-base. + List direct = SPINConstraints.check(violatingItem(), loadOntology()); + assertFalse(direct.isEmpty(), "SPINConstraints.check must fire on a raw ontapi OntModel (twirl re-bases internally)"); + } + +} diff --git a/src/test/java/com/atomgraph/linkeddatahub/vocabulary/VocabularyHolderTest.java b/src/test/java/com/atomgraph/linkeddatahub/vocabulary/VocabularyHolderTest.java new file mode 100644 index 000000000..95f10ddc6 --- /dev/null +++ b/src/test/java/com/atomgraph/linkeddatahub/vocabulary/VocabularyHolderTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2026 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.vocabulary; + +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.vocabulary.OWL; +import org.apache.jena.vocabulary.RDF; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Characterization snapshot for the vocabulary holders (representative: {@link DH}). + * + * All ~19 vocabulary classes follow the identical pattern — a static OntModel built with + * {@code ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null)} on which + * {@code createClass} / {@code createDatatypeProperty} mint typed terms. Phase A swaps the + * factory to {@code OntModelFactory.createModel(OntSpecification.OWL2_FULL_MEM)} and renames + * {@code createClass}→{@code createOntClass}, {@code createDatatypeProperty}→{@code createDataProperty}. + * + * Asserted via the migration-stable {@link Resource} interface: the term URIs and the + * {@code rdf:type owl:Class} / {@code rdf:type owl:DatatypeProperty} triples they produce + * must stay identical after the rename. + * + * @author Martynas Jusevičius {@literal } + */ +public class VocabularyHolderTest +{ + + @Test + public void testClassTermsHaveStableUriAndType() + { + Resource document = DH.Document; + assertEquals(DH.NS + "Document", document.getURI()); + assertTrue(document.hasProperty(RDF.type, OWL.Class), "DH.Document must be typed owl:Class"); + + Resource container = DH.Container; + assertEquals(DH.NS + "Container", container.getURI()); + assertTrue(container.hasProperty(RDF.type, OWL.Class), "DH.Container must be typed owl:Class"); + } + + @Test + public void testDatatypePropertyTermHasStableUriAndType() + { + Resource slug = DH.slug; + assertEquals(DH.NS + "slug", slug.getURI()); + assertTrue(slug.hasProperty(RDF.type, OWL.DatatypeProperty), "DH.slug must be typed owl:DatatypeProperty"); + } + +} From 90cad29393404c5d576a718e0e3b513b362f6d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Mon, 22 Jun 2026 11:36:40 +0200 Subject: [PATCH 14/14] HTTP test for bnodes after PATCH --- .../PATCH-blank-node-skolemized.sh | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100755 http-tests/document-hierarchy/PATCH-blank-node-skolemized.sh diff --git a/http-tests/document-hierarchy/PATCH-blank-node-skolemized.sh b/http-tests/document-hierarchy/PATCH-blank-node-skolemized.sh new file mode 100755 index 000000000..fec9f8343 --- /dev/null +++ b/http-tests/document-hierarchy/PATCH-blank-node-skolemized.sh @@ -0,0 +1,57 @@ +#!/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" + +# add agent to the writers group + +add-agent-to-group.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + --agent "$AGENT_URI" \ + "${ADMIN_BASE_URL}acl/groups/writers/" + +# PATCH the root with an INSERT that introduces a blank node. +# Use rdf:_99 to avoid colliding with existing rdf:_1..rdf:_8 in the test dataset. +# Expected: 204 No Content; the blank node is skolemized to a hash URI before persisting. + +update=$(cat < +PREFIX dct: + +INSERT +{ + <${END_USER_BASE_URL}> rdf:_99 _:bnode0 . + _:bnode0 dct:title "Blank node title" +} +WHERE +{} +EOF +) + +curl -k -w "%{http_code}\n" -o /dev/null -f -s \ + -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ + -X PATCH \ + -H "Content-Type: application/sparql-update" \ + "$END_USER_BASE_URL" \ + --data-binary "$update" \ +| grep -q "$STATUS_NO_CONTENT" + +# fetch the persisted graph and verify the blank node was skolemized to a hash URI + +response=$(curl -k -f -s -G \ + -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ + -H "Accept: application/n-triples" \ + "$END_USER_BASE_URL") + +rdf_99_line=$(echo "$response" | grep -E "^<${END_USER_BASE_URL}> " || true) + +[ -n "$rdf_99_line" ] || exit 1 + +# object of rdf:_99 must be a URI (<...>), not a blank node label (_:...) +! echo "$rdf_99_line" | grep -qE '_:[A-Za-z0-9]+ \.$' +echo "$rdf_99_line" | grep -qE '<\S+> \.$'