From c1f16de433c76be6ffb3c52e90198e77959e250f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Tue, 20 Jan 2026 10:01:13 +0100 Subject: [PATCH 1/7] [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 32e9246e3..6f440bfff 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.atomgraph linkeddatahub - 5.2.1 + 5.2.2-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.2.1 + linkeddatahub-2.1.1 From b2d3bce13403b345091753b6e4d3418efd85eee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 22 Jan 2026 12:55:59 +0100 Subject: [PATCH 2/7] Additional HTTP tests for file uploads --- http-tests/imports/PUT-file-format-browser.sh | 107 +++++++++++++++++ .../imports/PUT-file-format-explicit.sh | 113 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100755 http-tests/imports/PUT-file-format-browser.sh create mode 100755 http-tests/imports/PUT-file-format-explicit.sh diff --git a/http-tests/imports/PUT-file-format-browser.sh b/http-tests/imports/PUT-file-format-browser.sh new file mode 100755 index 000000000..cf5a20616 --- /dev/null +++ b/http-tests/imports/PUT-file-format-browser.sh @@ -0,0 +1,107 @@ +#!/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" + +pwd=$(realpath "$PWD") + +# 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/" + +# create test file with sample content + +test_file=$(mktemp) +echo "test,data,sample" > "$test_file" +echo "1,2,3" >> "$test_file" +echo "4,5,6" >> "$test_file" + +# upload file WITHOUT explicit media type (rely on browser detection via `file -b --mime-type`) + +file_doc=$(create-file.sh \ +-f "$AGENT_CERT_FILE" \ +-p "$AGENT_CERT_PWD" \ +-b "$END_USER_BASE_URL" \ +--title "Test File for Browser Media Type" \ +--file "$test_file") + +# get the file resource URI and initial dct:format + +file_doc_ntriples=$(get.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + --accept 'application/n-triples' \ + "$file_doc") + +file_uri=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_doc//\//\\/}> <(.*)> \./\1/p") + +# get initial SHA1 hash +initial_sha1=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_uri//\//\\/}> \"(.*)\" \./\1/p") + +# get initial dct:format (should be browser-detected) +initial_format=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_uri//\//\\/}> <(.*)> \./\1/p") + +# re-upload the same file to the same document with explicit media type: text/csv +# using PUT with RDF/POST multipart format +# IMPORTANT: Include explicit dct:format in RDF to simulate user editing the format field in the form + +rdf_post="" +rdf_post+="-F \"rdf=\"\n" +rdf_post+="-F \"sb=file\"\n" +rdf_post+="-F \"pu=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileName\"\n" +rdf_post+="-F \"ol=@${test_file};type=text/csv\"\n" +rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" +rdf_post+="-F \"ol=Test File for Browser Media Type\"\n" +rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" +rdf_post+="-F \"ou=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#FileDataObject\"\n" +rdf_post+="-F \"pu=http://purl.org/dc/terms/format\"\n" +rdf_post+="-F \"ou=http://www.sparontologies.net/mediatype/text/csv\"\n" +rdf_post+="-F \"su=${file_doc}\"\n" +rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" +rdf_post+="-F \"ol=Test File for Browser Media Type\"\n" +rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" +rdf_post+="-F \"ou=https://www.w3.org/ns/ldt/document-hierarchy#Item\"\n" +rdf_post+="-F \"pu=http://xmlns.com/foaf/0.1/primaryTopic\"\n" +rdf_post+="-F \"ob=file\"\n" +rdf_post+="-F \"pu=http://rdfs.org/sioc/ns#has_container\"\n" +rdf_post+="-F \"ou=${END_USER_BASE_URL}files/\"\n" + +echo -e "$rdf_post" | curl -f -v -s -k -X PUT -H "Accept: text/turtle" -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" -o /dev/null --config - "$file_doc" + +# get updated document + +updated_ntriples=$(get.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + --accept 'application/n-triples' \ + "$file_doc") + +# get updated SHA1 hash (should be same as initial) +updated_sha1=$(echo "$updated_ntriples" | sed -rn "s/<${file_uri//\//\\/}> \"(.*)\" \./\1/p") + +# get updated dct:format (should be text/csv) +updated_format=$(echo "$updated_ntriples" | sed -rn "s/<${file_uri//\//\\/}> <(.*)> \./\1/p") + +# verify SHA1 is unchanged (same file content) +if [ "$initial_sha1" != "$updated_sha1" ]; then + echo "ERROR: SHA1 hash changed! Initial: $initial_sha1, Updated: $updated_sha1" + exit 1 +fi + +# verify dct:format was updated to text/csv +if [[ ! "$updated_format" =~ text/csv ]]; then + echo "ERROR: Format should have been updated to text/csv but got: $updated_format" + echo "Initial format was: $initial_format" + exit 1 +fi + +# cleanup +rm -f "$test_file" diff --git a/http-tests/imports/PUT-file-format-explicit.sh b/http-tests/imports/PUT-file-format-explicit.sh new file mode 100755 index 000000000..eaa10401e --- /dev/null +++ b/http-tests/imports/PUT-file-format-explicit.sh @@ -0,0 +1,113 @@ +#!/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" + +pwd=$(realpath "$PWD") + +# 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/" + +# create test file with sample content + +test_file=$(mktemp) +echo "test,data,sample" > "$test_file" +echo "1,2,3" >> "$test_file" +echo "4,5,6" >> "$test_file" + +# upload file with explicit media type: text/plain + +file_doc=$(create-file.sh \ +-f "$AGENT_CERT_FILE" \ +-p "$AGENT_CERT_PWD" \ +-b "$END_USER_BASE_URL" \ +--title "Test File for Media Type Update" \ +--file "$test_file" \ +--file-content-type "text/plain") + +# get the file resource URI and initial dct:format + +file_doc_ntriples=$(get.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + --accept 'application/n-triples' \ + "$file_doc") + +file_uri=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_doc//\//\\/}> <(.*)> \./\1/p") + +# get initial SHA1 hash +initial_sha1=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_uri//\//\\/}> \"(.*)\" \./\1/p") + +# get initial dct:format +initial_format=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_uri//\//\\/}> <(.*)> \./\1/p") + +# verify initial format is text/plain +if [[ ! "$initial_format" =~ text/plain ]]; then + echo "ERROR: Initial format should contain text/plain but got: $initial_format" + exit 1 +fi + +# re-upload the same file to the same document with different explicit media type: text/csv +# using PUT with RDF/POST multipart format +# IMPORTANT: Include explicit dct:format in RDF to simulate user editing the format field in the form + +rdf_post="" +rdf_post+="-F \"rdf=\"\n" +rdf_post+="-F \"sb=file\"\n" +rdf_post+="-F \"pu=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileName\"\n" +rdf_post+="-F \"ol=@${test_file};type=text/csv\"\n" +rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" +rdf_post+="-F \"ol=Test File for Media Type Update\"\n" +rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" +rdf_post+="-F \"ou=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#FileDataObject\"\n" +rdf_post+="-F \"pu=http://purl.org/dc/terms/format\"\n" +rdf_post+="-F \"ou=http://www.sparontologies.net/mediatype/text/csv\"\n" +rdf_post+="-F \"su=${file_doc}\"\n" +rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" +rdf_post+="-F \"ol=Test File for Media Type Update\"\n" +rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" +rdf_post+="-F \"ou=https://www.w3.org/ns/ldt/document-hierarchy#Item\"\n" +rdf_post+="-F \"pu=http://xmlns.com/foaf/0.1/primaryTopic\"\n" +rdf_post+="-F \"ob=file\"\n" +rdf_post+="-F \"pu=http://rdfs.org/sioc/ns#has_container\"\n" +rdf_post+="-F \"ou=${END_USER_BASE_URL}files/\"\n" + +echo -e "$rdf_post" | curl -f -v -s -k -X PUT -H "Accept: text/turtle" -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" -o /dev/null --config - "$file_doc" + +# get updated document + +updated_ntriples=$(get.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + --accept 'application/n-triples' \ + "$file_doc") + +# get updated SHA1 hash (should be same as initial) +updated_sha1=$(echo "$updated_ntriples" | sed -rn "s/<${file_uri//\//\\/}> \"(.*)\" \./\1/p") + +# get updated dct:format (should be text/csv) +updated_format=$(echo "$updated_ntriples" | sed -rn "s/<${file_uri//\//\\/}> <(.*)> \./\1/p") + +# verify SHA1 is unchanged (same file content) +if [ "$initial_sha1" != "$updated_sha1" ]; then + echo "ERROR: SHA1 hash changed! Initial: $initial_sha1, Updated: $updated_sha1" + exit 1 +fi + +# verify dct:format was updated to text/csv +if [[ ! "$updated_format" =~ text/csv ]]; then + echo "ERROR: Format should have been updated to text/csv but got: $updated_format" + exit 1 +fi + +# cleanup +rm -f "$test_file" From 85262f0d3305a2ff8fd8ede2bbaf95c7cec97733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 22 Jan 2026 14:29:06 +0100 Subject: [PATCH 3/7] Test fix --- http-tests/imports/PUT-file-format-browser.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/http-tests/imports/PUT-file-format-browser.sh b/http-tests/imports/PUT-file-format-browser.sh index cf5a20616..dbc20e42c 100755 --- a/http-tests/imports/PUT-file-format-browser.sh +++ b/http-tests/imports/PUT-file-format-browser.sh @@ -51,7 +51,7 @@ initial_format=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_uri//\//\\/}> Date: Thu, 22 Jan 2026 15:23:08 +0100 Subject: [PATCH 4/7] HTTP test updates --- .../imports/PUT-file-format-explicit.sh | 55 ++++++++----------- ...e-format-browser.sh => PUT-file-format.sh} | 51 ++++++++--------- 2 files changed, 45 insertions(+), 61 deletions(-) rename http-tests/imports/{PUT-file-format-browser.sh => PUT-file-format.sh} (62%) diff --git a/http-tests/imports/PUT-file-format-explicit.sh b/http-tests/imports/PUT-file-format-explicit.sh index eaa10401e..3c9dffd8b 100755 --- a/http-tests/imports/PUT-file-format-explicit.sh +++ b/http-tests/imports/PUT-file-format-explicit.sh @@ -24,15 +24,20 @@ echo "test,data,sample" > "$test_file" echo "1,2,3" >> "$test_file" echo "4,5,6" >> "$test_file" +# generate slug for the file document + +slug=$(uuidgen | tr '[:upper:]' '[:lower:]') + # upload file with explicit media type: text/plain file_doc=$(create-file.sh \ --f "$AGENT_CERT_FILE" \ --p "$AGENT_CERT_PWD" \ --b "$END_USER_BASE_URL" \ ---title "Test File for Media Type Update" \ ---file "$test_file" \ ---file-content-type "text/plain") + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + -b "$END_USER_BASE_URL" \ + --title "Test File for Media Type Update" \ + --slug "$slug" \ + --file "$test_file" \ + --file-content-type "text/plain") # get the file resource URI and initial dct:format @@ -56,32 +61,18 @@ if [[ ! "$initial_format" =~ text/plain ]]; then exit 1 fi -# re-upload the same file to the same document with different explicit media type: text/csv -# using PUT with RDF/POST multipart format -# IMPORTANT: Include explicit dct:format in RDF to simulate user editing the format field in the form - -rdf_post="" -rdf_post+="-F \"rdf=\"\n" -rdf_post+="-F \"sb=file\"\n" -rdf_post+="-F \"pu=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileName\"\n" -rdf_post+="-F \"ol=@${test_file};type=text/csv\"\n" -rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" -rdf_post+="-F \"ol=Test File for Media Type Update\"\n" -rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" -rdf_post+="-F \"ou=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#FileDataObject\"\n" -rdf_post+="-F \"pu=http://purl.org/dc/terms/format\"\n" -rdf_post+="-F \"ou=http://www.sparontologies.net/mediatype/text/csv\"\n" -rdf_post+="-F \"su=${file_doc}\"\n" -rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" -rdf_post+="-F \"ol=Test File for Media Type Update\"\n" -rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" -rdf_post+="-F \"ou=https://www.w3.org/ns/ldt/document-hierarchy#Item\"\n" -rdf_post+="-F \"pu=http://xmlns.com/foaf/0.1/primaryTopic\"\n" -rdf_post+="-F \"ob=file\"\n" -rdf_post+="-F \"pu=http://rdfs.org/sioc/ns#has_container\"\n" -rdf_post+="-F \"ou=${END_USER_BASE_URL}files/\"\n" - -echo -e "$rdf_post" | curl -f -v -s -k -X PUT -H "Accept: text/turtle" -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" -o /dev/null --config - "$file_doc" +# re-upload the same file with same slug but different explicit media type: text/csv +# this simulates editing the file document through the UI and uploading a new file + +create-file.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + -b "$END_USER_BASE_URL" \ + --title "Test File for Media Type Update" \ + --slug "$slug" \ + --file "$test_file" \ + --file-content-type "text/csv" \ + > /dev/null # get updated document diff --git a/http-tests/imports/PUT-file-format-browser.sh b/http-tests/imports/PUT-file-format.sh similarity index 62% rename from http-tests/imports/PUT-file-format-browser.sh rename to http-tests/imports/PUT-file-format.sh index dbc20e42c..4a30ad9d6 100755 --- a/http-tests/imports/PUT-file-format-browser.sh +++ b/http-tests/imports/PUT-file-format.sh @@ -24,14 +24,19 @@ echo "test,data,sample" > "$test_file" echo "1,2,3" >> "$test_file" echo "4,5,6" >> "$test_file" +# generate slug for the file document + +slug=$(uuidgen | tr '[:upper:]' '[:lower:]') + # upload file WITHOUT explicit media type (rely on browser detection via `file -b --mime-type`) file_doc=$(create-file.sh \ --f "$AGENT_CERT_FILE" \ --p "$AGENT_CERT_PWD" \ --b "$END_USER_BASE_URL" \ ---title "Test File for Browser Media Type" \ ---file "$test_file") + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + -b "$END_USER_BASE_URL" \ + --title "Test File for Browser Media Type" \ + --slug "$slug" \ + --file "$test_file") # get the file resource URI and initial dct:format @@ -49,30 +54,18 @@ initial_sha1=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_uri//\//\\/}> <(.*)> \./\1/p") -# re-upload the same file to the same document with explicit media type: text/csv -# using PUT with RDF/POST multipart format -# IMPORTANT: Do NOT include explicit dct:format in RDF - test fallback to bodyPart.getMediaType() - -rdf_post="" -rdf_post+="-F \"rdf=\"\n" -rdf_post+="-F \"sb=file\"\n" -rdf_post+="-F \"pu=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileName\"\n" -rdf_post+="-F \"ol=@${test_file};type=text/csv\"\n" -rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" -rdf_post+="-F \"ol=Test File for Browser Media Type\"\n" -rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" -rdf_post+="-F \"ou=http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#FileDataObject\"\n" -rdf_post+="-F \"su=${file_doc}\"\n" -rdf_post+="-F \"pu=http://purl.org/dc/terms/title\"\n" -rdf_post+="-F \"ol=Test File for Browser Media Type\"\n" -rdf_post+="-F \"pu=http://www.w3.org/1999/02/22-rdf-syntax-ns#type\"\n" -rdf_post+="-F \"ou=https://www.w3.org/ns/ldt/document-hierarchy#Item\"\n" -rdf_post+="-F \"pu=http://xmlns.com/foaf/0.1/primaryTopic\"\n" -rdf_post+="-F \"ob=file\"\n" -rdf_post+="-F \"pu=http://rdfs.org/sioc/ns#has_container\"\n" -rdf_post+="-F \"ou=${END_USER_BASE_URL}files/\"\n" - -echo -e "$rdf_post" | curl -f -v -s -k -X PUT -H "Accept: text/turtle" -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" -o /dev/null --config - "$file_doc" +# re-upload the same file with same slug but WITH explicit media type: text/csv +# this simulates editing and uploading with a corrected format after browser auto-detection was wrong + +create-file.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + -b "$END_USER_BASE_URL" \ + --title "Test File for Browser Media Type" \ + --slug "$slug" \ + --file "$test_file" \ + --file-content-type "text/csv" \ + > /dev/null # get updated document From 31d433a2d87a5d4421f20c71786f87a49b89d53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Fri, 23 Jan 2026 11:16:58 +0100 Subject: [PATCH 5/7] Added missing class constant --- src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java index 1a528e187..259d34a0e 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/ACL.java @@ -60,6 +60,9 @@ public static String getURI() /** acl:Append access mode */ public static final OntClass Append = m_model.createClass( NS + "Append" ); + /** acl:Control access mode */ + public static final OntClass Control = m_model.createClass( NS + "Control" ); + /** acl:AuthenticatedAgent class */ public static final OntClass AuthenticatedAgent = m_model.createClass( NS + "AuthenticatedAgent" ); From 20fe469e307d27e3baf837e3f0858b5502b725c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sat, 24 Jan 2026 11:46:39 +0100 Subject: [PATCH 6/7] Ontology from upload deadlock test --- .../ontology-import-upload-no-deadlock.sh | 154 ++++++++++++++++++ .../admin/model/test-ontology-import.ttl | 17 ++ 2 files changed, 171 insertions(+) create mode 100755 http-tests/admin/model/ontology-import-upload-no-deadlock.sh create mode 100644 http-tests/admin/model/test-ontology-import.ttl diff --git a/http-tests/admin/model/ontology-import-upload-no-deadlock.sh b/http-tests/admin/model/ontology-import-upload-no-deadlock.sh new file mode 100755 index 000000000..af69d213f --- /dev/null +++ b/http-tests/admin/model/ontology-import-upload-no-deadlock.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Test that ontology imports of uploaded files do not cause deadlock +# This verifies the fix for circular dependency when: +# 1. Request arrives for /uploads/xyz +# 2. OntologyFilter intercepts it and loads ontology +# 3. Ontology has owl:imports for /uploads/xyz +# 4. Jena FileManager makes HTTP request to /uploads/xyz +# 5. Would cause infinite loop/deadlock without the fix + +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" + +pwd=$(realpath "$PWD") + +# add agent to the writers group so they can upload files + +add-agent-to-group.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + --agent "$AGENT_URI" \ + "${ADMIN_BASE_URL}acl/groups/writers/" + +# Step 1: Upload an RDF file + +file_content_type="text/turtle" + +file_doc=$(create-file.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + -b "$END_USER_BASE_URL" \ + --title "Test ontology for upload import" \ + --file "$pwd/test-ontology-import.ttl" \ + --file-content-type "${file_content_type}") + +# Step 2: Extract the uploaded file URI (content-addressed) + +file_doc_ntriples=$(get.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + --accept 'application/n-triples' \ + "$file_doc") + +upload_uri=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_doc//\//\\/}> <(.*)> \./\1/p") + +echo "Uploaded file URI: $upload_uri" + +# Verify the uploaded file is accessible before we add it as an import +curl -k -f -s \ + -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ + -H "Accept: ${file_content_type}" \ + "$upload_uri" > /dev/null + +echo "Upload file is accessible" + +# Step 3: Add the uploaded file as an owl:import to the namespace ontology + +namespace_doc="${END_USER_BASE_URL}ns" +namespace="${namespace_doc}#" +ontology_doc="${ADMIN_BASE_URL}ontologies/namespace/" + +add-ontology-import.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + --import "$upload_uri" \ + "$ontology_doc" + +echo "Added owl:import of uploaded file to namespace ontology" + +# Step 4: Clear the namespace ontology from memory to force reload on next request + +clear-ontology.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + -b "$ADMIN_BASE_URL" \ + --ontology "$namespace" + +echo "Cleared ontology cache to force reload" + +# Step 5: Make a request that triggers ontology loading +# This would cause a deadlock without the OntologyFilter fix +# Use portable timeout implementation (works on both macOS and Linux) + +echo "Making request to trigger ontology loading (testing for deadlock)..." + +# Portable timeout function - works on both macOS and Linux +request_pid="" +( + curl -k -f -s \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -H "Accept: application/n-triples" \ + "$namespace_doc" > /dev/null +) & +request_pid=$! + +# Wait up to 30 seconds for the request to complete +timeout_seconds=30 +elapsed=0 +while kill -0 "$request_pid" 2>/dev/null; do + if [ $elapsed -ge $timeout_seconds ]; then + kill -9 "$request_pid" 2>/dev/null || true + echo "ERROR: Request timed out after ${timeout_seconds} seconds - deadlock detected!" + exit 1 + fi + sleep 1 + ((elapsed++)) +done + +# Check if curl succeeded +wait "$request_pid" +curl_exit_code=$? +if [ $curl_exit_code -ne 0 ]; then + echo "ERROR: Request failed with exit code $curl_exit_code" + exit 1 +fi + +echo "Request completed successfully in ${elapsed}s (no deadlock)" + +# Step 6: Verify the import is present in the loaded ontology + +curl -k -f -s \ + -H "Accept: application/n-triples" \ + "$namespace_doc" \ +| grep "<${namespace}> <${upload_uri}>" > /dev/null + +echo "Verified owl:import is present in namespace ontology" + +# Step 7: Verify the uploaded file is still accessible after ontology loading + +curl -k -f -s \ + -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ + -H "Accept: ${file_content_type}" \ + "$upload_uri" > /dev/null + +echo "Uploaded file is still accessible after ontology import" + +# Step 8: Verify that the imported ontology content is accessible via the namespace document +# This confirms the import was actually loaded (not just skipped) + +curl -k -f -s \ + -G \ + -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ + -H 'Accept: application/sparql-results+xml' \ + --data-urlencode "query=SELECT * { ?p ?o }" \ + "$namespace_doc" \ +| grep 'Test Class' > /dev/null + +echo "Verified imported ontology content is accessible via SPARQL" + +echo "✓ All tests passed - no deadlock detected when importing uploaded files in ontology" diff --git a/http-tests/admin/model/test-ontology-import.ttl b/http-tests/admin/model/test-ontology-import.ttl new file mode 100644 index 000000000..24361b035 --- /dev/null +++ b/http-tests/admin/model/test-ontology-import.ttl @@ -0,0 +1,17 @@ +@prefix : . +@prefix owl: . +@prefix rdfs: . +@prefix xsd: . + +: a owl:Ontology ; + rdfs:label "Test ontology for upload import" ; + rdfs:comment "This ontology is uploaded to test that ontology imports of uploaded files do not cause deadlock" . + +:TestClass a owl:Class ; + rdfs:label "Test Class" ; + rdfs:comment "A test class to verify ontology was loaded" . + +:testProperty a owl:DatatypeProperty ; + rdfs:label "Test Property" ; + rdfs:domain :TestClass ; + rdfs:range xsd:string . From 7f4b8f419d3d31cc482d60a07cd788c84312e149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Sun, 25 Jan 2026 14:58:47 +0100 Subject: [PATCH 7/7] Ignored paths in `OntologyFilter` (#269) * Ignored paths in `OntologyFilter` * Debug test * Debug test * Debug test * Removed debug output * Debug test * Debug test * Removed debug output --- .../ontology-import-upload-no-deadlock.sh | 62 ++----------------- .../server/filter/request/OntologyFilter.java | 31 ++++++++++ 2 files changed, 35 insertions(+), 58 deletions(-) diff --git a/http-tests/admin/model/ontology-import-upload-no-deadlock.sh b/http-tests/admin/model/ontology-import-upload-no-deadlock.sh index af69d213f..939da9687 100755 --- a/http-tests/admin/model/ontology-import-upload-no-deadlock.sh +++ b/http-tests/admin/model/ontology-import-upload-no-deadlock.sh @@ -47,16 +47,12 @@ file_doc_ntriples=$(get.sh \ upload_uri=$(echo "$file_doc_ntriples" | sed -rn "s/<${file_doc//\//\\/}> <(.*)> \./\1/p") -echo "Uploaded file URI: $upload_uri" - # Verify the uploaded file is accessible before we add it as an import curl -k -f -s \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -H "Accept: ${file_content_type}" \ "$upload_uri" > /dev/null -echo "Upload file is accessible" - # Step 3: Add the uploaded file as an owl:import to the namespace ontology namespace_doc="${END_USER_BASE_URL}ns" @@ -69,8 +65,6 @@ add-ontology-import.sh \ --import "$upload_uri" \ "$ontology_doc" -echo "Added owl:import of uploaded file to namespace ontology" - # Step 4: Clear the namespace ontology from memory to force reload on next request clear-ontology.sh \ @@ -79,66 +73,22 @@ clear-ontology.sh \ -b "$ADMIN_BASE_URL" \ --ontology "$namespace" -echo "Cleared ontology cache to force reload" - -# Step 5: Make a request that triggers ontology loading -# This would cause a deadlock without the OntologyFilter fix -# Use portable timeout implementation (works on both macOS and Linux) - -echo "Making request to trigger ontology loading (testing for deadlock)..." - -# Portable timeout function - works on both macOS and Linux -request_pid="" -( - curl -k -f -s \ - -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ - -H "Accept: application/n-triples" \ - "$namespace_doc" > /dev/null -) & -request_pid=$! - -# Wait up to 30 seconds for the request to complete -timeout_seconds=30 -elapsed=0 -while kill -0 "$request_pid" 2>/dev/null; do - if [ $elapsed -ge $timeout_seconds ]; then - kill -9 "$request_pid" 2>/dev/null || true - echo "ERROR: Request timed out after ${timeout_seconds} seconds - deadlock detected!" - exit 1 - fi - sleep 1 - ((elapsed++)) -done - -# Check if curl succeeded -wait "$request_pid" -curl_exit_code=$? -if [ $curl_exit_code -ne 0 ]; then - echo "ERROR: Request failed with exit code $curl_exit_code" - exit 1 -fi - -echo "Request completed successfully in ${elapsed}s (no deadlock)" - -# Step 6: Verify the import is present in the loaded ontology +# Step 5: Verify the import is present in the loaded ontology +# This request also triggers ontology loading and would detect deadlock curl -k -f -s \ -H "Accept: application/n-triples" \ "$namespace_doc" \ | grep "<${namespace}> <${upload_uri}>" > /dev/null -echo "Verified owl:import is present in namespace ontology" - -# Step 7: Verify the uploaded file is still accessible after ontology loading +# Step 6: Verify the uploaded file is still accessible after ontology loading curl -k -f -s \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -H "Accept: ${file_content_type}" \ "$upload_uri" > /dev/null -echo "Uploaded file is still accessible after ontology import" - -# Step 8: Verify that the imported ontology content is accessible via the namespace document +# Step 7: Verify that the imported ontology content is accessible via the namespace document # This confirms the import was actually loaded (not just skipped) curl -k -f -s \ @@ -148,7 +98,3 @@ curl -k -f -s \ --data-urlencode "query=SELECT * { ?p ?o }" \ "$namespace_doc" \ | grep 'Test Class' > /dev/null - -echo "Verified imported ontology content is accessible via SPARQL" - -echo "✓ All tests passed - no deadlock detected when importing uploaded files in ontology" 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 c996d5214..0390a989b 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 @@ -54,12 +54,43 @@ public class OntologyFilter implements ContainerRequestFilter private static final Logger log = LoggerFactory.getLogger(OntologyFilter.class); + /** + * Paths that should not trigger ontology loading to avoid circular dependencies. + * + * When an ontology contains owl:imports pointing to URIs within these paths, + * loading the ontology would trigger HTTP requests to those URIs. If those requests + * are intercepted by this filter, it creates a circular dependency: + * + * 1. Request arrives for /uploads/xyz + * 2. OntologyFilter intercepts it and loads ontology + * 3. Ontology has owl:imports for /uploads/xyz + * 4. Jena FileManager makes HTTP request to /uploads/xyz + * 5. OntologyFilter intercepts it again → infinite loop/deadlock + * + * Additionally, uploaded files are binary/RDF content that don't require + * ontology context for their serving logic. + */ + private static final java.util.Set IGNORED_PATH_PREFIXES = java.util.Set.of( + "uploads/" + ); + @Inject com.atomgraph.linkeddatahub.Application system; @Override public void filter(ContainerRequestContext crc) throws IOException { + String path = crc.getUriInfo().getPath(); + + // Skip ontology loading for paths that may be referenced in owl:imports + // to prevent circular dependency deadlocks during ontology resolution + if (IGNORED_PATH_PREFIXES.stream().anyMatch(path::startsWith)) + { + if (log.isTraceEnabled()) log.trace("Skipping ontology loading for path: {}", path); + crc.setProperty(OWL.Ontology.getURI(), Optional.empty()); + return; + } + crc.setProperty(OWL.Ontology.getURI(), getOntology(crc)); }