From a81aab039e0087828cd8a0d44e41bf10b73e0417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Tue, 29 Sep 2026 22:26:20 +0200 Subject: [PATCH 1/3] Extract the vocabularies and document shapes into linkeddatahub-rdf The RDF a client has to send to create a container, add a chart or grant an authorization was described in static buildModel methods hanging off picocli @Command classes, reachable only by depending on the CLI - so Web-Algebra's ldh-* operations, which build the same documents through REST-VKG's execution context rather than through Jersey, had no way to share them and would have had to restate every shape. A shape restated is a shape that drifts. rdf/ now builds com.atomgraph:linkeddatahub-rdf with Jena as its only dependency: what is shared is the shape of the request body, not how it is sent, so neither consumer inherits the other's transport. The eighteen builders move off the command classes into classes named for what they build - Documents (container, item), Blocks, Views, Queries, Services, Imports, Acl and Ontologies - with BaseCommand.createSubject becoming Subjects.of, since fifteen of them needed it. The nine vocabulary classes and Slugs, SequenceNumbers, Digests and Updates move across unchanged. Commands are now argument parsing and a call. URIRewriter splits rather than moving whole: childURI, encodeSlug and adminBase are conventions the platform itself applies and belong in the library as URIs, while rewrite is the --proxy option sending a request somewhere other than where its URI says, which stays a CLI concern with origin now private. The platform keeps its own com.atomgraph.linkeddatahub.vocabulary, still duplicated with the library's. Collapsing them would make the platform depend on rdf/, and the Dockerfile builds the webapp from COPY src and COPY pom.xml alone, so the dependency has to point away from the platform rather than at it. cli/pom.xml resolves the library by ${project.version}; make cli installs it first, and make cli-version and release.sh's sync_cli_version now version both poms together, since the two must move as one or the CLI stops resolving. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 1 + CLAUDE.md | 33 ++- cli/README.md | 6 + cli/pom.xml | 7 + .../linkeddatahub/cli/BaseCommand.java | 15 -- .../cli/command/AddConstruct.java | 37 +--- .../linkeddatahub/cli/command/AddFile.java | 4 +- .../cli/command/AddGenericService.java | 41 +--- .../cli/command/AddPackageImport.java | 2 +- .../cli/command/AddResultSetChart.java | 39 +--- .../linkeddatahub/cli/command/AddSelect.java | 5 +- .../linkeddatahub/cli/command/AddView.java | 36 +--- .../cli/command/CreateContainer.java | 57 +---- .../linkeddatahub/cli/command/CreateItem.java | 42 +--- .../cli/command/ListPackages.java | 2 +- .../cli/command/RemovePackageImport.java | 2 +- .../cli/command/admin/AddOntologyImport.java | 2 +- .../command/admin/acl/AddAgentToGroup.java | 2 +- .../admin/acl/CreateAuthorization.java | 65 +----- .../cli/command/admin/acl/CreateGroup.java | 44 +--- .../cli/command/admin/acl/MakePublic.java | 8 +- .../command/admin/ontologies/AddClass.java | 37 +--- .../admin/ontologies/AddConstructor.java | 38 +--- .../ontologies/AddPropertyConstraint.java | 33 +-- .../admin/ontologies/AddRestriction.java | 35 +-- .../command/admin/ontologies/AddSelect.java | 5 +- .../admin/ontologies/CreateOntology.java | 44 +--- .../admin/ontologies/ImportOntology.java | 25 +-- .../cli/command/content/AddObjectBlock.java | 38 +--- .../cli/command/content/AddXHTMLBlock.java | 35 +-- .../cli/command/content/RemoveBlock.java | 2 +- .../cli/command/imports/AddCSVImport.java | 37 +--- .../cli/command/imports/AddRDFImport.java | 37 +--- .../cli/command/imports/ImportCSV.java | 2 +- .../cli/command/imports/ImportRDF.java | 2 +- .../linkeddatahub/cli/http/LDHClient.java | 2 +- .../linkeddatahub/cli/util/PushPlan.java | 5 +- .../linkeddatahub/cli/util/URIRewriter.java | 58 +---- .../linkeddatahub/cli/PushOutputTest.java | 4 +- .../cli/util/URIRewriterTest.java | 25 --- make/config.mk | 2 +- make/local.mk | 20 +- rdf/pom.xml | 86 ++++++++ .../com/atomgraph/linkeddatahub/rdf/Acl.java | 120 +++++++++++ .../atomgraph/linkeddatahub/rdf/Blocks.java | 96 +++++++++ .../atomgraph/linkeddatahub/rdf}/Digests.java | 2 +- .../linkeddatahub/rdf/Documents.java | 105 +++++++++ .../atomgraph/linkeddatahub/rdf/Imports.java | 98 +++++++++ .../linkeddatahub/rdf/Ontologies.java | 199 ++++++++++++++++++ .../atomgraph/linkeddatahub/rdf/Queries.java | 66 ++++++ .../linkeddatahub/rdf}/SequenceNumbers.java | 2 +- .../atomgraph/linkeddatahub/rdf/Services.java | 69 ++++++ .../atomgraph/linkeddatahub/rdf}/Slugs.java | 2 +- .../atomgraph/linkeddatahub/rdf/Subjects.java | 51 +++++ .../com/atomgraph/linkeddatahub/rdf/URIs.java | 87 ++++++++ .../atomgraph/linkeddatahub/rdf}/Updates.java | 2 +- .../atomgraph/linkeddatahub/rdf/Views.java | 94 +++++++++ .../linkeddatahub/rdf/vocabulary}/A.java | 2 +- .../linkeddatahub/rdf/vocabulary}/AC.java | 2 +- .../linkeddatahub/rdf/vocabulary}/ACL.java | 2 +- .../linkeddatahub/rdf/vocabulary}/DH.java | 2 +- .../linkeddatahub/rdf/vocabulary}/LDH.java | 2 +- .../linkeddatahub/rdf/vocabulary}/NFO.java | 2 +- .../linkeddatahub/rdf/vocabulary}/SD.java | 2 +- .../linkeddatahub/rdf/vocabulary}/SP.java | 2 +- .../linkeddatahub/rdf/vocabulary}/SPIN.java | 2 +- .../linkeddatahub/rdf}/ModelBuildersTest.java | 70 +++--- .../rdf}/SequenceNumbersTest.java | 2 +- .../atomgraph/linkeddatahub/rdf/URIsTest.java | 54 +++++ .../linkeddatahub/rdf}/UpdatesTest.java | 2 +- release.sh | 22 +- 71 files changed, 1330 insertions(+), 853 deletions(-) create mode 100644 rdf/pom.xml create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Acl.java create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Blocks.java rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/util => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf}/Digests.java (97%) create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Documents.java create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Imports.java create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Ontologies.java create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/util => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf}/SequenceNumbers.java (98%) create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Services.java rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/util => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf}/Slugs.java (96%) create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Subjects.java create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/URIs.java rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/sparql => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf}/Updates.java (99%) create mode 100644 rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Views.java rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/A.java (96%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/AC.java (95%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/ACL.java (97%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/DH.java (96%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/LDH.java (98%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/NFO.java (96%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/SD.java (97%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/SP.java (96%) rename {cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab => rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary}/SPIN.java (96%) rename {cli/src/test/java/com/atomgraph/linkeddatahub/cli/command => rdf/src/test/java/com/atomgraph/linkeddatahub/rdf}/ModelBuildersTest.java (80%) rename {cli/src/test/java/com/atomgraph/linkeddatahub/cli/util => rdf/src/test/java/com/atomgraph/linkeddatahub/rdf}/SequenceNumbersTest.java (98%) create mode 100644 rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/URIsTest.java rename {cli/src/test/java/com/atomgraph/linkeddatahub/cli/sparql => rdf/src/test/java/com/atomgraph/linkeddatahub/rdf}/UpdatesTest.java (98%) diff --git a/.gitignore b/.gitignore index ac8300205..d8d44e3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ /tests/ui/node_modules /tests/ui/out /tests/ui/test-results +/rdf/target/ diff --git a/CLAUDE.md b/CLAUDE.md index 723a28009..e41324b3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,8 +39,9 @@ make down # Stop the services make down -- -v # Stop and remove volumes make drop # Complete reset (down -v, then wipe local dirs) -make cli # Build the ldh CLI, print the PATH export to run -make cli-version # Set cli/pom.xml to the platform version in pom.xml +make rdf # Install the linkeddatahub-rdf library +make cli # Build the ldh CLI (installs rdf first), print the PATH export +make cli-version # Set rdf/ and cli/ poms to the platform version in pom.xml ``` ### Testing @@ -155,15 +156,35 @@ into a shaded `cli/target/ldh.jar` that `cli/bin/ldh` launches. See `cli/README. script → command table and the behavioral differences from the scripts. ```bash -cd cli && mvn package && export PATH="$PWD/bin:$PATH" +make cli && export PATH="$PWD/cli/bin:$PATH" ldh create container --parent "$LDH_BASE" --title "Some" --slug some ldh admin add agent --agent "$AGENT_URI" "${ADMIN_BASE}acl/groups/writers/" ``` -`cli/` is not a module of the platform reactor (the root pom is the webapp artifact, so it cannot -carry ``), but it shares the platform's version: `release.sh` runs `versions:set` on it -around both release bumps, and `make cli-version` re-aligns it if it drifts. +## The RDF library + +`rdf/` builds `com.atomgraph:linkeddatahub-rdf` — the vocabularies (`com.atomgraph.linkeddatahub.rdf.vocabulary`) +and the document shapes the HTTP API accepts (`Documents`, `Blocks`, `Views`, `Queries`, `Services`, +`Imports`, `Acl`, `Ontologies`, plus the SPARQL `Updates`). Jena and nothing else: no Jersey, no +picocli. Every consumer talks to the platform over HTTP its own way, so what is shared is the shape +of the request body, not how it is sent. + +Two consumers today — the CLI, and Web-Algebra's `ldh-*` operations in `../REST-VKG` — which is why +the builders live here rather than on the picocli command classes that used to own them. A shape +described in one place cannot drift between them. + +The platform keeps its own `com.atomgraph.linkeddatahub.vocabulary`, still duplicated with this +library's. Collapsing them would make the platform depend on `rdf/`, and the Dockerfile builds the +webapp from `COPY src` + `COPY pom.xml` alone — so the dependency has to point away from the +platform, not at it. + +Neither `rdf/` nor `cli/` is a module of the platform reactor (the root pom is the webapp artifact, +so it cannot carry ``), but both share the platform's version: `release.sh` runs +`versions:set` on them around both release bumps, and `make cli-version` re-aligns them if they +drift. `cli/pom.xml` resolves the library by `${project.version}`, so the two move together. +`make cli` installs `rdf/` first; building `cli/` on its own needs `make rdf` to have run at least +once since the last version bump. `LDH_CERT_FILE`, `LDH_CERT_PASSWORD`, `LDH_BASE` and `LDH_PROXY` supply defaults for `-c`, `-p`, `-b` and `--proxy`. `-c/--cert` takes either format the agent's credential comes in — a PKCS12 diff --git a/cli/README.md b/cli/README.md index cef2eb3a7..08d8bfbfb 100644 --- a/cli/README.md +++ b/cli/README.md @@ -46,11 +46,17 @@ make cli which prints the `export PATH=...` line to run afterwards. It is the equivalent of: ```bash +cd rdf && mvn install && cd .. cd cli mvn package export PATH="$PWD/bin:$PATH" ``` +The `rdf/` step installs `com.atomgraph:linkeddatahub-rdf`, which holds the vocabularies and the +document shapes the commands build — shared with Web-Algebra's `ldh-*` operations, so the two +cannot drift. It is resolved by `${project.version}`, so it has to be installed again after a +version bump; `make cli` does that for you. + This produces the self-contained `target/ldh.jar`, which the `cli/bin/ldh` launcher runs. The launcher prefers `LDH_JAR`, then a jar sitting beside it (the release archive layout), then `../target/ldh.jar` (the source checkout layout). diff --git a/cli/pom.xml b/cli/pom.xml index 4b7b1a567..d59cc38a4 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -18,6 +18,13 @@ + + + com.atomgraph + linkeddatahub-rdf + ${project.version} + info.picocli picocli diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/BaseCommand.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/BaseCommand.java index 972860578..19e4a0f50 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/BaseCommand.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/BaseCommand.java @@ -32,7 +32,6 @@ import java.util.concurrent.Callable; 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.riot.Lang; import org.apache.jena.riot.RDFLanguages; import org.apache.jena.riot.RDFParser; @@ -124,20 +123,6 @@ protected static void put(LDHClient client, URI target, Model model) HttpException.check(target, client.put(target, Entity.entity(model, TEXT_TURTLE_TYPE), ACCEPT_TURTLE)).close(); } - /** - * Returns the subject resource for an appended description: the --uri value - * resolved against the target document URI, or a fresh blank node when not given. - * - * @param model model to create the resource in - * @param target target document URI - * @param uri --uri option value (absolute or relative, can be null) - * @return subject resource - */ - protected static Resource createSubject(Model model, URI target, String uri) - { - return uri != null ? model.createResource(target.resolve(uri).toString()) : model.createResource(); - } - /** * Parses an RDF stream into a model, resolving relative URIs against the base URI * (the equivalent of the scripts' turtle --base piping). diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddConstruct.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddConstruct.java index ec84ddecf..38f5f7eab 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddConstruct.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddConstruct.java @@ -19,16 +19,11 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.LDHClient; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +import com.atomgraph.linkeddatahub.rdf.Queries; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -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.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -88,33 +83,7 @@ public Integer call() throws Exception */ public static void core(LDHClient client, URI target, String uri, String title, String queryText, URI service, String description) { - post(client, target, buildModel(target, uri, SP.Construct, title, queryText, service, description)); - } - - /** - * Builds a SPIN query description. - * - * @param target target document URI - * @param uri query URI (optional) - * @param queryType SPIN query class (sp:Construct or sp:Select) - * @param title query title - * @param queryText query string - * @param service SPARQL service URI (optional) - * @param description query description (optional) - * @return query model - */ - public static Model buildModel(URI target, String uri, Resource queryType, String title, String queryText, URI service, String description) - { - Model model = ModelFactory.createDefaultModel(); - - Resource query = createSubject(model, target, uri). - addProperty(RDF.type, queryType). - addProperty(DCTerms.title, title). - addProperty(SP.text, queryText); - if (service != null) query.addProperty(LDH.service, model.createResource(service.toString())); - if (description != null) query.addProperty(DCTerms.description, description); - - return model; + post(client, target, Queries.query(target, uri, SP.Construct, title, queryText, service, description)); } } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddFile.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddFile.java index 7872d4529..a3f5df769 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddFile.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddFile.java @@ -20,8 +20,8 @@ import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.http.LDHClient; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Digests; -import com.atomgraph.linkeddatahub.cli.vocab.NFO; +import com.atomgraph.linkeddatahub.rdf.Digests; +import com.atomgraph.linkeddatahub.rdf.vocabulary.NFO; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.core.MediaType; import java.io.IOException; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddGenericService.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddGenericService.java index 554b41f0c..7952b5307 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddGenericService.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddGenericService.java @@ -18,14 +18,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.A; -import com.atomgraph.linkeddatahub.cli.vocab.SD; +import com.atomgraph.linkeddatahub.rdf.Services; import java.net.URI; -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.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -72,41 +66,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, title, endpoint, graphStore, authUser, authPwd, description)); + post(getClient(), target, Services.service(target, uri, title, endpoint, graphStore, authUser, authPwd, description)); print(target); return 0; } - /** - * Builds the service description. - * - * @param target target document URI - * @param uri service URI (optional) - * @param title service title - * @param endpoint SPARQL endpoint URI - * @param graphStore Graph Store Protocol endpoint URI (optional) - * @param authUser HTTP Basic auth username (optional) - * @param authPwd HTTP Basic auth password (optional) - * @param description service description (optional) - * @return service model - */ - public static Model buildModel(URI target, String uri, String title, URI endpoint, URI graphStore, String authUser, String authPwd, String description) - { - Model model = ModelFactory.createDefaultModel(); - - Resource service = createSubject(model, target, uri). - addProperty(RDF.type, SD.Service). - addProperty(DCTerms.title, title). - addProperty(SD.endpoint, model.createResource(endpoint.toString())). - addProperty(SD.supportedLanguage, SD.SPARQL11Query). - addProperty(SD.supportedLanguage, SD.SPARQL11Update); - if (graphStore != null) service.addProperty(A.graphStore, model.createResource(graphStore.toString())); - if (authUser != null) service.addProperty(A.authUser, authUser); - if (authPwd != null) service.addProperty(A.authPwd, authPwd); - if (description != null) service.addProperty(DCTerms.description, description); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddPackageImport.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddPackageImport.java index 698c3652c..189b72270 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddPackageImport.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddPackageImport.java @@ -19,7 +19,7 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.sparql.Updates; +import com.atomgraph.linkeddatahub.rdf.Updates; import java.net.URI; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddResultSetChart.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddResultSetChart.java index ee9ebeb9c..cb3eefb0a 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddResultSetChart.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddResultSetChart.java @@ -18,14 +18,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SPIN; +import com.atomgraph.linkeddatahub.rdf.Views; import java.net.URI; -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.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -72,39 +66,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, title, query, chartType, categoryVarName, seriesVarName, description)); + post(getClient(), target, Views.resultSetChart(target, uri, title, query, chartType, categoryVarName, seriesVarName, description)); print(target); return 0; } - /** - * Builds the chart description. - * - * @param target target document URI - * @param uri chart URI (optional) - * @param title chart title - * @param query SELECT query URI - * @param chartType chart type URI - * @param categoryVarName category variable name - * @param seriesVarName series variable name - * @param description chart description (optional) - * @return chart model - */ - public static Model buildModel(URI target, String uri, String title, URI query, URI chartType, String categoryVarName, String seriesVarName, String description) - { - Model model = ModelFactory.createDefaultModel(); - - Resource chart = createSubject(model, target, uri). - addProperty(RDF.type, LDH.ResultSetChart). - addProperty(DCTerms.title, title). - addProperty(SPIN.query, model.createResource(query.toString())). - addProperty(LDH.chartType, model.createResource(chartType.toString())). - addProperty(LDH.categoryVarName, categoryVarName). - addProperty(LDH.seriesVarName, seriesVarName); - if (description != null) chart.addProperty(DCTerms.description, description); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddSelect.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddSelect.java index cbcd857ca..0e3530f48 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddSelect.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddSelect.java @@ -18,7 +18,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +import com.atomgraph.linkeddatahub.rdf.Queries; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -62,7 +63,7 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, AddConstruct.buildModel(target, uri, SP.Select, title, Files.readString(queryFile), service, description)); + post(getClient(), target, Queries.query(target, uri, SP.Select, title, Files.readString(queryFile), service, description)); print(target); return 0; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddView.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddView.java index ab21f69ce..d6c313370 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddView.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/AddView.java @@ -18,15 +18,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.AC; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SPIN; +import com.atomgraph.linkeddatahub.rdf.Views; import java.net.URI; -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.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -67,35 +60,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, query, title, description, mode)); + post(getClient(), target, Views.view(target, uri, query, title, description, mode)); print(target); return 0; } - /** - * Builds the view description. - * - * @param target target document URI - * @param uri view URI (optional) - * @param query SELECT query URI - * @param title view title (optional) - * @param description view description (optional) - * @param mode layout mode URI (optional) - * @return view model - */ - public static Model buildModel(URI target, String uri, URI query, String title, String description, URI mode) - { - Model model = ModelFactory.createDefaultModel(); - - Resource view = createSubject(model, target, uri). - addProperty(RDF.type, LDH.View). - addProperty(SPIN.query, model.createResource(query.toString())); - if (title != null) view.addProperty(DCTerms.title, title); - if (description != null) view.addProperty(DCTerms.description, description); - if (mode != null) view.addProperty(AC.mode, model.createResource(mode.toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateContainer.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateContainer.java index 1a61d2014..b1ec21a46 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateContainer.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateContainer.java @@ -18,19 +18,10 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; -import com.atomgraph.linkeddatahub.cli.util.URIRewriter; -import com.atomgraph.linkeddatahub.cli.vocab.AC; -import com.atomgraph.linkeddatahub.cli.vocab.DH; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SPIN; +import com.atomgraph.linkeddatahub.rdf.Documents; +import com.atomgraph.linkeddatahub.rdf.Slugs; +import com.atomgraph.linkeddatahub.rdf.URIs; import java.net.URI; -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.sparql.vocabulary.FOAF; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -73,49 +64,11 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - URI doc = URIRewriter.childURI(parent, slug != null ? slug : Slugs.defaultSlug()); - put(getClient(), doc, buildModel(doc, title, description, block, mode, primaryTopic)); + URI doc = URIs.childURI(parent, slug != null ? slug : Slugs.defaultSlug()); + put(getClient(), doc, Documents.container(doc, title, description, block, mode, primaryTopic)); print(doc); return 0; } - /** - * Builds the container document model with its first content block: the given block URI, - * a children view with an explicit mode, or the default children view. - * - * @param doc document URI - * @param title document title - * @param description document description (optional) - * @param block content block URI (optional) - * @param mode children view mode URI (optional, ignored when block is given) - * @param primaryTopic URI of the document's primary topic, relative or absolute (optional) - * @return document model - */ - public static Model buildModel(URI doc, String title, String description, URI block, URI mode, String primaryTopic) - { - Model model = ModelFactory.createDefaultModel(); - - Resource container = model.createResource(doc.toString()). - addProperty(RDF.type, DH.Container). - addProperty(DCTerms.title, title); - - if (block != null) container.addProperty(RDF.li(1), model.createResource(block.toString())); - else if (mode != null) container.addProperty(RDF.li(1), model.createResource(). - addProperty(RDF.type, LDH.Object). - addProperty(RDF.value, model.createResource(). - addProperty(RDF.type, LDH.View). - addProperty(SPIN.query, LDH.SelectChildren). - addProperty(AC.mode, model.createResource(mode.toString())))); - else container.addProperty(RDF.li(1), model.createResource(). - addProperty(RDF.type, LDH.Object). - addProperty(RDF.value, LDH.ChildrenView)); - - if (description != null) container.addProperty(DCTerms.description, description); - // See CreateItem.buildModel: resolved against the document, and singular by the vocabulary. - if (primaryTopic != null) container.addProperty(FOAF.primaryTopic, model.createResource(doc.resolve(primaryTopic).toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateItem.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateItem.java index 40b3dd3f7..526342dc3 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateItem.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/CreateItem.java @@ -18,16 +18,10 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; -import com.atomgraph.linkeddatahub.cli.util.URIRewriter; -import com.atomgraph.linkeddatahub.cli.vocab.DH; +import com.atomgraph.linkeddatahub.rdf.Documents; +import com.atomgraph.linkeddatahub.rdf.Slugs; +import com.atomgraph.linkeddatahub.rdf.URIs; import java.net.URI; -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.sparql.vocabulary.FOAF; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -64,37 +58,11 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - URI doc = URIRewriter.childURI(container, slug != null ? slug : Slugs.defaultSlug()); - put(getClient(), doc, buildModel(doc, title, description, primaryTopic)); + URI doc = URIs.childURI(container, slug != null ? slug : Slugs.defaultSlug()); + put(getClient(), doc, Documents.item(doc, title, description, primaryTopic)); print(doc); return 0; } - /** - * Builds the item document model. - * - * @param doc document URI - * @param title document title - * @param description document description (optional) - * @param primaryTopic URI of the document's primary topic, relative or absolute (optional) - * @return document model - */ - public static Model buildModel(URI doc, String title, String description, String primaryTopic) - { - Model model = ModelFactory.createDefaultModel(); - - Resource item = model.createResource(doc.toString()). - addProperty(RDF.type, DH.Item). - addProperty(DCTerms.title, title); - if (description != null) item.addProperty(DCTerms.description, description); - // Resolved against the document, so the conventional fragment topic is "#this" and a - // document about something described elsewhere takes that resource's absolute URI. - // Singular because foaf:primaryTopic is an owl:FunctionalProperty: a second value would - // not mean a second topic, it would entail the two topics are the same resource. - if (primaryTopic != null) item.addProperty(FOAF.primaryTopic, model.createResource(doc.resolve(primaryTopic).toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/ListPackages.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/ListPackages.java index 8823f8549..63713c656 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/ListPackages.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/ListPackages.java @@ -19,7 +19,7 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.net.URI; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/RemovePackageImport.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/RemovePackageImport.java index 191f06b91..ab717ffb8 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/RemovePackageImport.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/RemovePackageImport.java @@ -19,7 +19,7 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.sparql.Updates; +import com.atomgraph.linkeddatahub.rdf.Updates; import java.net.URI; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/AddOntologyImport.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/AddOntologyImport.java index f54d246ec..15e8d939d 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/AddOntologyImport.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/AddOntologyImport.java @@ -18,7 +18,7 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; -import com.atomgraph.linkeddatahub.cli.sparql.Updates; +import com.atomgraph.linkeddatahub.rdf.Updates; import java.net.URI; import picocli.CommandLine.Command; import picocli.CommandLine.Option; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/AddAgentToGroup.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/AddAgentToGroup.java index a2a59dce7..fbcbfaedd 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/AddAgentToGroup.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/AddAgentToGroup.java @@ -18,7 +18,7 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; -import com.atomgraph.linkeddatahub.cli.sparql.Updates; +import com.atomgraph.linkeddatahub.rdf.Updates; import java.net.URI; import picocli.CommandLine.Command; import picocli.CommandLine.Option; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateAuthorization.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateAuthorization.java index 8fbc01c6d..1fabd986b 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateAuthorization.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateAuthorization.java @@ -18,21 +18,14 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; -import com.atomgraph.linkeddatahub.cli.util.URIRewriter; -import com.atomgraph.linkeddatahub.cli.vocab.ACL; -import com.atomgraph.linkeddatahub.cli.vocab.DH; +import com.atomgraph.linkeddatahub.rdf.Acl; +import com.atomgraph.linkeddatahub.rdf.Slugs; +import com.atomgraph.linkeddatahub.rdf.URIs; +import com.atomgraph.linkeddatahub.rdf.vocabulary.ACL; import java.net.URI; import java.util.ArrayList; import java.util.List; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; -import org.apache.jena.sparql.vocabulary.FOAF; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -100,7 +93,7 @@ public Integer call() throws Exception if (!append && !control && !read && !write) throw new ParameterException(getSpec().commandLine(), "At least one of '--append', '--control', '--read', '--write' is required"); - URI doc = URIRewriter.childURI(URI.create(base + "acl/authorizations/"), slug != null ? slug : Slugs.defaultSlug()); + URI doc = URIs.childURI(URI.create(base + "acl/authorizations/"), slug != null ? slug : Slugs.defaultSlug()); List modes = new ArrayList<>(); if (append) modes.add(ACL.Append); @@ -108,56 +101,10 @@ public Integer call() throws Exception if (read) modes.add(ACL.Read); if (write) modes.add(ACL.Write); - put(getClient(), doc, buildModel(doc, uri, label, comment, agents, agentClasses, agentGroups, to, toAllIn, modes)); + put(getClient(), doc, Acl.authorization(doc, uri, label, comment, agents, agentClasses, agentGroups, to, toAllIn, modes)); print(doc); return 0; } - /** - * Builds the authorization document model. - * - * @param doc document URI - * @param uri authorization URI (optional, blank node if null) - * @param label authorization label - * @param comment authorization comment (optional) - * @param agents authorized agent URIs - * @param agentClasses authorized agent class URIs - * @param agentGroups authorized agent group URIs - * @param to accessed document URIs - * @param toAllIn accessed document class URIs - * @param modes granted access modes - * @return document model - */ - public static Model buildModel(URI doc, String uri, String label, String comment, - List agents, List agentClasses, List agentGroups, - List to, List toAllIn, List modes) - { - Model model = ModelFactory.createDefaultModel(); - - Resource auth = createSubject(model, doc, uri). - addProperty(RDF.type, ACL.Authorization). - addProperty(RDFS.label, label); - if (comment != null) auth.addProperty(RDFS.comment, comment); - - model.createResource(doc.toString()). - addProperty(RDF.type, DH.Item). - addProperty(FOAF.primaryTopic, auth). - addProperty(DCTerms.title, label); - - addResourceValues(auth, ACL.agent, agents); - addResourceValues(auth, ACL.agentClass, agentClasses); - addResourceValues(auth, ACL.agentGroup, agentGroups); - addResourceValues(auth, ACL.accessTo, to); - addResourceValues(auth, ACL.accessToClass, toAllIn); - modes.forEach(mode -> auth.addProperty(ACL.mode, mode)); - - return model; - } - - static void addResourceValues(Resource subject, Property property, List values) - { - values.forEach(value -> subject.addProperty(property, subject.getModel().createResource(value.toString()))); - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateGroup.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateGroup.java index ca35618fb..a6988a66a 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateGroup.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/CreateGroup.java @@ -18,17 +18,11 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; -import com.atomgraph.linkeddatahub.cli.util.URIRewriter; -import com.atomgraph.linkeddatahub.cli.vocab.DH; +import com.atomgraph.linkeddatahub.rdf.Acl; +import com.atomgraph.linkeddatahub.rdf.Slugs; +import com.atomgraph.linkeddatahub.rdf.URIs; import java.net.URI; import java.util.List; -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.sparql.vocabulary.FOAF; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -64,40 +58,12 @@ public class CreateGroup extends BaseCommand public Integer call() throws Exception { URI base = baseMixin.require(getSpec()); - URI doc = URIRewriter.childURI(URI.create(base + "acl/groups/"), slug != null ? slug : Slugs.defaultSlug()); + URI doc = URIs.childURI(URI.create(base + "acl/groups/"), slug != null ? slug : Slugs.defaultSlug()); - put(getClient(), doc, buildModel(doc, uri, name, description, members)); + put(getClient(), doc, Acl.group(doc, uri, name, description, members)); print(doc); return 0; } - /** - * Builds the group document model. - * - * @param doc document URI - * @param uri group URI (optional, blank node if null) - * @param name group name - * @param description group description (optional) - * @param members member agent URIs - * @return document model - */ - public static Model buildModel(URI doc, String uri, String name, String description, List members) - { - Model model = ModelFactory.createDefaultModel(); - - Resource group = createSubject(model, doc, uri). - addProperty(RDF.type, FOAF.Group). - addProperty(FOAF.name, name); - if (description != null) group.addProperty(DCTerms.description, description); - members.forEach(member -> group.addProperty(FOAF.member, model.createResource(member.toString()))); - - model.createResource(doc.toString()). - addProperty(RDF.type, DH.Item). - addProperty(FOAF.primaryTopic, group). - addProperty(DCTerms.title, name); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/MakePublic.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/MakePublic.java index a041e3521..86f76da27 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/MakePublic.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/acl/MakePublic.java @@ -19,8 +19,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.sparql.Updates; -import com.atomgraph.linkeddatahub.cli.util.URIRewriter; +import com.atomgraph.linkeddatahub.rdf.URIs; +import com.atomgraph.linkeddatahub.rdf.Updates; import java.net.URI; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -41,7 +41,7 @@ public class MakePublic extends BaseCommand public Integer call() throws Exception { URI base = baseMixin.require(getSpec()); - URI adminBase = URIRewriter.adminBase(base); + URI adminBase = URIs.adminBase(base); URI target = URI.create(adminBase + "acl/authorizations/public/"); HttpException.check(target, getClient().patch(target, Updates.makePublic(base, adminBase))).close(); @@ -53,7 +53,7 @@ public Integer call() throws Exception protected URI getEffectiveProxy() { // the request targets the admin app, so the proxy origin gets the admin subdomain too - return getProxyMixin().getProxy() != null ? URIRewriter.adminBase(getProxyMixin().getProxy()) : null; + return getProxyMixin().getProxy() != null ? URIs.adminBase(getProxyMixin().getProxy()) : null; } } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddClass.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddClass.java index 830330957..dc579add3 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddClass.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddClass.java @@ -18,16 +18,10 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.SPIN; +import com.atomgraph.linkeddatahub.rdf.Ontologies; import java.net.URI; import java.util.ArrayList; import java.util.List; -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.OWL; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -71,37 +65,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, label, comment, constructor, constraint, superClasses)); + post(getClient(), target, Ontologies.owlClass(target, uri, label, comment, constructor, constraint, superClasses)); print(target); return 0; } - /** - * Builds the class description. - * - * @param target target document URI - * @param uri class URI (optional) - * @param label class label - * @param comment class comment (optional) - * @param constructor constructor query URI (optional) - * @param constraint constraint URI (optional) - * @param superClasses superclass URIs - * @return class model - */ - public static Model buildModel(URI target, String uri, String label, String comment, URI constructor, URI constraint, List superClasses) - { - Model model = ModelFactory.createDefaultModel(); - - Resource cls = createSubject(model, target, uri). - addProperty(RDF.type, OWL.Class). - addProperty(RDFS.label, label); - if (comment != null) cls.addProperty(RDFS.comment, comment); - if (constructor != null) cls.addProperty(SPIN.constructor, model.createResource(constructor.toString())); - if (constraint != null) cls.addProperty(SPIN.constraint, model.createResource(constraint.toString())); - superClasses.forEach(superClass -> cls.addProperty(RDFS.subClassOf, model.createResource(superClass.toString()))); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddConstructor.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddConstructor.java index ec1d1eb60..47c63cfc6 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddConstructor.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddConstructor.java @@ -18,16 +18,11 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +import com.atomgraph.linkeddatahub.rdf.Ontologies; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -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.RDF; -import org.apache.jena.vocabulary.RDFS; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -65,37 +60,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, SP.Construct, label, Files.readString(queryFile), null, comment)); + post(getClient(), target, Ontologies.constructor(target, uri, SP.Construct, label, Files.readString(queryFile), null, comment)); print(target); return 0; } - /** - * Builds an ontology SPIN query description (labeled with rdfs:label, - * unlike the dct:title-based document queries). - * - * @param target target document URI - * @param uri query URI (optional) - * @param queryType SPIN query class (sp:Construct or sp:Select) - * @param label query label - * @param queryText query string - * @param service SPARQL service URI (optional) - * @param comment query comment (optional) - * @return query model - */ - public static Model buildModel(URI target, String uri, Resource queryType, String label, String queryText, URI service, String comment) - { - Model model = ModelFactory.createDefaultModel(); - - Resource query = createSubject(model, target, uri). - addProperty(RDF.type, queryType). - addProperty(RDFS.label, label). - addProperty(SP.text, queryText); - if (comment != null) query.addProperty(RDFS.comment, comment); - if (service != null) query.addProperty(LDH.service, model.createResource(service.toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddPropertyConstraint.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddPropertyConstraint.java index eafc655b2..f33f0608d 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddPropertyConstraint.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddPropertyConstraint.java @@ -18,14 +18,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +import com.atomgraph.linkeddatahub.rdf.Ontologies; import java.net.URI; -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.RDF; -import org.apache.jena.vocabulary.RDFS; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -63,33 +57,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, label, property, comment)); + post(getClient(), target, Ontologies.propertyConstraint(target, uri, label, property, comment)); print(target); return 0; } - /** - * Builds the constraint description. - * - * @param target target document URI - * @param uri constraint URI (optional) - * @param label constraint label - * @param property required property URI - * @param comment constraint comment (optional) - * @return constraint model - */ - public static Model buildModel(URI target, String uri, String label, URI property, String comment) - { - Model model = ModelFactory.createDefaultModel(); - - Resource constraint = createSubject(model, target, uri). - addProperty(RDF.type, LDH.MissingPropertyValue). - addProperty(RDFS.label, label). - addProperty(SP.arg1, model.createResource(property.toString())); - if (comment != null) constraint.addProperty(RDFS.comment, comment); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddRestriction.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddRestriction.java index 9ac8ae79c..10cbd2613 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddRestriction.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddRestriction.java @@ -18,13 +18,9 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; +import com.atomgraph.linkeddatahub.rdf.Ontologies; import java.net.URI; -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.OWL; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -68,37 +64,10 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, buildModel(target, uri, label, comment, onProperty, allValuesFrom, hasValue)); + post(getClient(), target, Ontologies.restriction(target, uri, label, comment, onProperty, allValuesFrom, hasValue)); print(target); return 0; } - /** - * Builds the restriction description. - * - * @param target target document URI - * @param uri restriction URI (optional) - * @param label restriction label - * @param comment restriction comment (optional) - * @param onProperty restricted property URI (optional) - * @param allValuesFrom value class URI (optional) - * @param hasValue value resource URI (optional) - * @return restriction model - */ - public static Model buildModel(URI target, String uri, String label, String comment, URI onProperty, URI allValuesFrom, URI hasValue) - { - Model model = ModelFactory.createDefaultModel(); - - Resource restriction = createSubject(model, target, uri). - addProperty(RDF.type, OWL.Restriction). - addProperty(RDFS.label, label); - if (comment != null) restriction.addProperty(RDFS.comment, comment); - if (onProperty != null) restriction.addProperty(OWL.onProperty, model.createResource(onProperty.toString())); - if (allValuesFrom != null) restriction.addProperty(OWL.allValuesFrom, model.createResource(allValuesFrom.toString())); - if (hasValue != null) restriction.addProperty(OWL.hasValue, model.createResource(hasValue.toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddSelect.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddSelect.java index 323e9ab34..10467adfe 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddSelect.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/AddSelect.java @@ -18,7 +18,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +import com.atomgraph.linkeddatahub.rdf.Ontologies; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -62,7 +63,7 @@ public Integer call() throws Exception { baseMixin.require(getSpec()); // required by the script interface - post(getClient(), target, AddConstructor.buildModel(target, uri, SP.Select, label, Files.readString(queryFile), service, comment)); + post(getClient(), target, Ontologies.constructor(target, uri, SP.Select, label, Files.readString(queryFile), service, comment)); print(target); return 0; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/CreateOntology.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/CreateOntology.java index 9b4312e75..0431d906b 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/CreateOntology.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/CreateOntology.java @@ -18,18 +18,10 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; -import com.atomgraph.linkeddatahub.cli.util.URIRewriter; -import com.atomgraph.linkeddatahub.cli.vocab.DH; +import com.atomgraph.linkeddatahub.rdf.Ontologies; +import com.atomgraph.linkeddatahub.rdf.Slugs; +import com.atomgraph.linkeddatahub.rdf.URIs; import java.net.URI; -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.sparql.vocabulary.FOAF; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.OWL; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.RDFS; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -62,38 +54,12 @@ public class CreateOntology extends BaseCommand public Integer call() throws Exception { URI base = baseMixin.require(getSpec()); - URI doc = URIRewriter.childURI(URI.create(base + "ontologies/"), slug != null ? slug : Slugs.defaultSlug()); + URI doc = URIs.childURI(URI.create(base + "ontologies/"), slug != null ? slug : Slugs.defaultSlug()); - put(getClient(), doc, buildModel(doc, uri, label, comment)); + put(getClient(), doc, Ontologies.ontology(doc, uri, label, comment)); print(doc); return 0; } - /** - * Builds the ontology document model. - * - * @param doc document URI - * @param uri ontology URI (optional, blank node if null) - * @param label ontology label - * @param comment ontology comment (optional) - * @return document model - */ - public static Model buildModel(URI doc, String uri, String label, String comment) - { - Model model = ModelFactory.createDefaultModel(); - - Resource ontology = createSubject(model, doc, uri). - addProperty(RDF.type, OWL.Ontology). - addProperty(RDFS.label, label); - if (comment != null) ontology.addProperty(RDFS.comment, comment); - - model.createResource(doc.toString()). - addProperty(RDF.type, DH.Item). - addProperty(FOAF.primaryTopic, ontology). - addProperty(DCTerms.title, label); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/ImportOntology.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/ImportOntology.java index ebde26ec5..b187dda50 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/ImportOntology.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/admin/ontologies/ImportOntology.java @@ -19,7 +19,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +import com.atomgraph.linkeddatahub.rdf.Ontologies; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; import jakarta.ws.rs.core.Form; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -27,9 +28,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; 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.sparql.vocabulary.FOAF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -77,7 +76,7 @@ public Integer call() throws Exception // the vocabulary goes into the target first, so the CONSTRUCT has it to read and it stays afterwards post(getClient(), graph, vocabulary); post(getClient(), graph, construct(base, query, graph)); - post(getClient(), graph, buildAnnotationModel(graph, source)); + post(getClient(), graph, Ontologies.annotation(graph, source)); print(graph); @@ -141,22 +140,4 @@ protected Model construct(URI base, String query, URI graph) } } - /** - * Builds the arc saying what the document is about. The document is not the ontology, so it takes - * no owl:Ontology type of its own: the vocabulary stored alongside carries that. - * - * @param graph target document URI - * @param source imported ontology URI - * @return primary topic model - */ - public static Model buildAnnotationModel(URI graph, URI source) - { - Model model = ModelFactory.createDefaultModel(); - - model.createResource(graph.toString()). - addProperty(FOAF.primaryTopic, model.createResource(source.toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddObjectBlock.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddObjectBlock.java index 534554861..71a5a1a94 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddObjectBlock.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddObjectBlock.java @@ -19,18 +19,13 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.SequenceNumbers; -import com.atomgraph.linkeddatahub.cli.vocab.AC; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; +import com.atomgraph.linkeddatahub.rdf.Blocks; +import com.atomgraph.linkeddatahub.rdf.SequenceNumbers; import jakarta.ws.rs.core.Response; import java.net.URI; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Property; -import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -69,7 +64,7 @@ public class AddObjectBlock extends BaseCommand @Override public Integer call() throws Exception { - post(getClient(), target, buildModel(target, nextSequenceProperty(), uri, value, title, description, mode)); + post(getClient(), target, Blocks.object(target, nextSequenceProperty(), uri, value, title, description, mode)); print(target); return 0; @@ -91,31 +86,4 @@ protected Property nextSequenceProperty() return SequenceNumbers.nextSequenceProperty(current, ResourceFactory.createResource(target.toString())); } - /** - * Builds the object block description. - * - * @param target target document URI - * @param seq membership property (rdf:_N) - * @param uri block URI (optional) - * @param value object resource URI - * @param title block title (optional) - * @param description block description (optional) - * @param mode layout mode URI (optional) - * @return block model - */ - public static Model buildModel(URI target, Property seq, String uri, URI value, String title, String description, URI mode) - { - Model model = ModelFactory.createDefaultModel(); - - Resource block = createSubject(model, target, uri). - addProperty(RDF.type, LDH.Object). - addProperty(RDF.value, model.createResource(value.toString())); - model.createResource(target.toString()).addProperty(seq, block); - if (title != null) block.addProperty(DCTerms.title, title); - if (description != null) block.addProperty(DCTerms.description, description); - if (mode != null) block.addProperty(AC.mode, model.createResource(mode.toString())); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddXHTMLBlock.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddXHTMLBlock.java index 4237b7a98..ebb5c595c 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddXHTMLBlock.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/AddXHTMLBlock.java @@ -19,17 +19,13 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.SequenceNumbers; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; +import com.atomgraph.linkeddatahub.rdf.Blocks; +import com.atomgraph.linkeddatahub.rdf.SequenceNumbers; import jakarta.ws.rs.core.Response; import java.net.URI; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Property; -import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.vocabulary.DCTerms; -import org.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -72,35 +68,10 @@ public Integer call() throws Exception } Property seq = SequenceNumbers.nextSequenceProperty(current, ResourceFactory.createResource(target.toString())); - post(getClient(), target, buildModel(target, seq, uri, value, title, description)); + post(getClient(), target, Blocks.xhtml(target, seq, uri, value, title, description)); print(target); return 0; } - /** - * Builds the XHTML block description. - * - * @param target target document URI - * @param seq membership property (rdf:_N) - * @param uri block URI (optional) - * @param value XHTML content - * @param title block title (optional) - * @param description block description (optional) - * @return block model - */ - public static Model buildModel(URI target, Property seq, String uri, String value, String title, String description) - { - Model model = ModelFactory.createDefaultModel(); - - Resource block = createSubject(model, target, uri). - addProperty(RDF.type, LDH.XHTML). - addProperty(RDF.value, model.createTypedLiteral(value, RDF.dtXMLLiteral)); - model.createResource(target.toString()).addProperty(seq, block); - if (title != null) block.addProperty(DCTerms.title, title); - if (description != null) block.addProperty(DCTerms.description, description); - - return model; - } - } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/RemoveBlock.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/RemoveBlock.java index ee61864de..8c8362491 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/RemoveBlock.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/content/RemoveBlock.java @@ -18,7 +18,7 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.HttpException; -import com.atomgraph.linkeddatahub.cli.sparql.Updates; +import com.atomgraph.linkeddatahub.rdf.Updates; import java.net.URI; import picocli.CommandLine.Command; import picocli.CommandLine.Option; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddCSVImport.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddCSVImport.java index 6a60f63e0..d9edc4547 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddCSVImport.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddCSVImport.java @@ -19,14 +19,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.LDHClient; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SPIN; +import com.atomgraph.linkeddatahub.rdf.Imports; import java.net.URI; -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.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -90,34 +84,7 @@ public Integer call() throws Exception */ public static void core(LDHClient client, URI target, String uri, String title, URI query, URI file, String delimiter, String description) { - post(client, target, buildModel(target, uri, title, query, file, delimiter, description)); - } - - /** - * Builds the CSV import description. - * - * @param target target document URI - * @param uri import URI (optional) - * @param title import title - * @param query transformation query URI - * @param file uploaded file URI - * @param delimiter CSV delimiter - * @param description import description (optional) - * @return import model - */ - public static Model buildModel(URI target, String uri, String title, URI query, URI file, String delimiter, String description) - { - Model model = ModelFactory.createDefaultModel(); - - Resource csvImport = createSubject(model, target, uri). - addProperty(RDF.type, LDH.CSVImport). - addProperty(DCTerms.title, title). - addProperty(SPIN.query, model.createResource(query.toString())). - addProperty(LDH.file, model.createResource(file.toString())). - addProperty(LDH.delimiter, delimiter); - if (description != null) csvImport.addProperty(DCTerms.description, description); - - return model; + post(client, target, Imports.csv(target, uri, title, query, file, delimiter, description)); } } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddRDFImport.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddRDFImport.java index f7d825da2..c57807d6b 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddRDFImport.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/AddRDFImport.java @@ -19,14 +19,8 @@ import com.atomgraph.linkeddatahub.cli.BaseCommand; import com.atomgraph.linkeddatahub.cli.http.LDHClient; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.vocab.LDH; -import com.atomgraph.linkeddatahub.cli.vocab.SD; -import com.atomgraph.linkeddatahub.cli.vocab.SPIN; +import com.atomgraph.linkeddatahub.rdf.Imports; import java.net.URI; -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.apache.jena.vocabulary.RDF; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -91,34 +85,7 @@ public Integer call() throws Exception */ public static void core(LDHClient client, URI target, String uri, String title, URI file, URI query, URI graph, String description) { - post(client, target, buildModel(target, uri, title, file, query, graph, description)); - } - - /** - * Builds the RDF import description. - * - * @param target target document URI - * @param uri import URI (optional) - * @param title import title - * @param file uploaded file URI - * @param query transformation query URI (optional) - * @param graph target named graph URI (optional) - * @param description import description (optional) - * @return import model - */ - public static Model buildModel(URI target, String uri, String title, URI file, URI query, URI graph, String description) - { - Model model = ModelFactory.createDefaultModel(); - - Resource rdfImport = createSubject(model, target, uri). - addProperty(RDF.type, LDH.RDFImport). - addProperty(DCTerms.title, title). - addProperty(LDH.file, model.createResource(file.toString())); - if (graph != null) rdfImport.addProperty(SD.name, model.createResource(graph.toString())); - if (query != null) rdfImport.addProperty(SPIN.query, model.createResource(query.toString())); - if (description != null) rdfImport.addProperty(DCTerms.description, description); - - return model; + post(client, target, Imports.rdf(target, uri, title, file, query, graph, description)); } } diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportCSV.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportCSV.java index b40415c2f..86372a82a 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportCSV.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportCSV.java @@ -20,7 +20,7 @@ import com.atomgraph.linkeddatahub.cli.command.AddConstruct; import com.atomgraph.linkeddatahub.cli.command.AddFile; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; +import com.atomgraph.linkeddatahub.rdf.Slugs; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportRDF.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportRDF.java index 08088f41f..c93bde717 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportRDF.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/command/imports/ImportRDF.java @@ -20,7 +20,7 @@ import com.atomgraph.linkeddatahub.cli.command.AddConstruct; import com.atomgraph.linkeddatahub.cli.command.AddFile; import com.atomgraph.linkeddatahub.cli.mixin.BaseMixin; -import com.atomgraph.linkeddatahub.cli.util.Slugs; +import com.atomgraph.linkeddatahub.rdf.Slugs; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/http/LDHClient.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/http/LDHClient.java index 15c07feec..af71a62fb 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/http/LDHClient.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/http/LDHClient.java @@ -19,7 +19,6 @@ import com.atomgraph.core.MediaTypes; import com.atomgraph.core.client.GraphStoreClient; import com.atomgraph.linkeddatahub.cli.util.URIRewriter; -import org.apache.jena.rdf.model.Model; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; @@ -31,6 +30,7 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import java.net.URI; +import org.apache.jena.rdf.model.Model; /** * Graph Store Protocol client for LinkedDataHub documents (direct graph identification), diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/PushPlan.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/PushPlan.java index a9c5eda4e..3469a1b6d 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/PushPlan.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/PushPlan.java @@ -16,6 +16,7 @@ package com.atomgraph.linkeddatahub.cli.util; +import com.atomgraph.linkeddatahub.rdf.URIs; import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -127,7 +128,7 @@ private static void walk(Path dir, URI url, Path root, Deque ignores, else { String name = doc.getFileName().toString(); - URI docURI = isRootDocument(doc, root) ? url : URIRewriter.childURI(url, stem(name)); + URI docURI = isRootDocument(doc, root) ? url : URIs.childURI(url, stem(name)); steps.add(new Step(Kind.DOCUMENT, doc, relative(doc, root, false), docURI, documentLang(name).getContentType().getContentTypeStr())); } } @@ -138,7 +139,7 @@ private static void walk(Path dir, URI url, Path root, Deque ignores, for (Path subdir : dirs) { if (isIgnored(subdir, true, ignores)) steps.add(skip(subdir, root, true)); - else walk(subdir, URIRewriter.childURI(url, subdir.getFileName().toString()), root, ignores, steps); + else walk(subdir, URIs.childURI(url, subdir.getFileName().toString()), root, ignores, steps); } } finally diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/URIRewriter.java b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/URIRewriter.java index 593181c03..2bb2a603d 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/URIRewriter.java +++ b/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/URIRewriter.java @@ -17,10 +17,16 @@ package com.atomgraph.linkeddatahub.cli.util; import java.net.URI; -import java.nio.charset.StandardCharsets; /** - * URI manipulation helpers matching the conventions of the bin/ shell scripts. + * Sends a request somewhere other than where its URI says. + *

+ * This is the --proxy option: a document's URI identifies it, but the host that + * answers for it may be a different one - the client-certificate port, say, or a tunnel. The + * logical URI stays in the request; only the origin it is sent to changes. + *

+ * A CLI concern, not a document-shape one, which is why it stays here while the URI conventions + * the platform itself applies live in {@link com.atomgraph.linkeddatahub.rdf.URIs}. * * @author Martynas Jusevičius {@literal } */ @@ -48,57 +54,11 @@ public static URI rewrite(URI uri, URI proxy) * @param uri URI * @return origin string, e.g. https://localhost:4443 */ - public static String origin(URI uri) + private static String origin(URI uri) { if (uri.getScheme() == null || uri.getRawAuthority() == null) throw new IllegalArgumentException("URI '" + uri + "' is not absolute"); return uri.getScheme() + "://" + uri.getRawAuthority(); } - /** - * Converts an end-user application base URI to the base URI of its admin application - * by prefixing the host with the admin. subdomain. - * - * @param base end-user base URI - * @return admin base URI - */ - public static URI adminBase(URI base) - { - return URI.create(base.toString().replaceFirst("://", "://admin.")); - } - - /** - * Percent-encodes a string as a URI path segment. All characters except RFC 3986 - * unreserved ones are encoded, including /. - * - * @param slug path segment - * @return encoded path segment - */ - public static String encodeSlug(String slug) - { - StringBuilder sb = new StringBuilder(); - - for (byte b : slug.getBytes(StandardCharsets.UTF_8)) - { - char c = (char)(b & 0xFF); - if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || - c == '-' || c == '.' || c == '_' || c == '~') sb.append(c); - else sb.append('%').append(String.format("%02X", b & 0xFF)); - } - - return sb.toString(); - } - - /** - * Builds the URI of a child document from the parent container URI and a path segment slug. - * - * @param parent parent container URI (with trailing slash) - * @param slug path segment - * @return child document URI (with trailing slash) - */ - public static URI childURI(URI parent, String slug) - { - return URI.create(parent.toString() + encodeSlug(slug) + "/"); - } - } diff --git a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/PushOutputTest.java b/cli/src/test/java/com/atomgraph/linkeddatahub/cli/PushOutputTest.java index e5ce8fa3b..30ec07e41 100644 --- a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/PushOutputTest.java +++ b/cli/src/test/java/com/atomgraph/linkeddatahub/cli/PushOutputTest.java @@ -16,9 +16,9 @@ package com.atomgraph.linkeddatahub.cli; -import com.atomgraph.linkeddatahub.cli.http.StubServer; import com.atomgraph.linkeddatahub.cli.http.StubServer.Request; -import com.atomgraph.linkeddatahub.cli.util.Digests; +import com.atomgraph.linkeddatahub.cli.http.StubServer; +import com.atomgraph.linkeddatahub.rdf.Digests; import java.io.IOException; import java.io.StringWriter; import java.net.URI; diff --git a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/URIRewriterTest.java b/cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/URIRewriterTest.java index 222a90863..276d4b536 100644 --- a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/URIRewriterTest.java +++ b/cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/URIRewriterTest.java @@ -48,29 +48,4 @@ public void rewriteRejectsRelativeURI() () -> URIRewriter.rewrite(URI.create("/relative/path"), URI.create("https://localhost:8443"))); } - @Test - public void adminBasePrefixesHostWithAdminSubdomain() - { - assertEquals(URI.create("https://admin.localhost:4443/"), URIRewriter.adminBase(URI.create("https://localhost:4443/"))); - } - - @Test - public void encodeSlugKeepsUnreservedCharacters() - { - assertEquals("abc-._~123", URIRewriter.encodeSlug("abc-._~123")); - } - - @Test - public void encodeSlugEncodesReservedAndNonASCII() - { - assertEquals("a%20b%2F%C4%87", URIRewriter.encodeSlug("a b/ć")); - } - - @Test - public void childURIAppendsEncodedSlugAndSlash() - { - assertEquals(URI.create("https://localhost:4443/some/my%20item/"), - URIRewriter.childURI(URI.create("https://localhost:4443/some/"), "my item")); - } - } diff --git a/make/config.mk b/make/config.mk index 3875143b9..dfa550006 100644 --- a/make/config.mk +++ b/make/config.mk @@ -4,7 +4,7 @@ # has no app to install and compiles its client stylesheet from source rather than from a # published image. Everything it adds lives in make/local.mk. -LOCAL_TARGETS := sef release cli cli-version tests ui-tests ui-tests-install load-tests +LOCAL_TARGETS := sef release rdf cli cli-version tests ui-tests ui-tests-install load-tests # only the deployment configuration is RDF worth parsing here; the rest of the tree is source VALIDATE_PATHS := config datasets diff --git a/make/local.mk b/make/local.mk index 54491bfab..c218bbfe3 100644 --- a/make/local.mk +++ b/make/local.mk @@ -15,17 +15,25 @@ sef: release: ./release.sh -# Set cli/pom.xml to the platform version in pom.xml. The CLI ships with the platform release, so -# the two versions are kept in step; release.sh runs this around the release version bumps, and this -# target is for drift and for manual SNAPSHOT bumps +# Set rdf/pom.xml and cli/pom.xml to the platform version in pom.xml. Both ship with the platform +# release, so all three versions are kept in step; release.sh runs this around the release version +# bumps, and this target is for drift and for manual SNAPSHOT bumps cli-version: @version=$$(mvn -q help:evaluate -Dexpression=project.version -DforceStdout); \ - cd cli && mvn -B -q versions:set -DnewVersion="$$version" -DgenerateBackupPoms=false && \ - echo "cli/pom.xml set to $$version" + for project in rdf cli; do \ + (cd $$project && mvn -B -q versions:set -DnewVersion="$$version" -DgenerateBackupPoms=false) && \ + echo "$$project/pom.xml set to $$version"; \ + done + +# Build the linkeddatahub-rdf library: the vocabularies and the document shapes the API accepts. +# Installed rather than packaged because the CLI - and Web-Algebra's ldh-* operations, in their own +# repository - resolve it as an ordinary Maven dependency. +rdf: + cd rdf && mvn -B install # Build the ldh CLI (requires Java 21 and Maven) and print the line that puts it on $PATH. # Released versions are also attached to the GitHub release, which needs neither. -cli: +cli: rdf cd cli && mvn -B package @echo @echo "Add the ldh launcher to your \$$PATH:" diff --git a/rdf/pom.xml b/rdf/pom.xml new file mode 100644 index 000000000..6c4986686 --- /dev/null +++ b/rdf/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + com.atomgraph + linkeddatahub-rdf + 6.0.1-SNAPSHOT + jar + + LinkedDataHub RDF + The RDF shapes LinkedDataHub documents are made of, and the vocabularies they are written in + https://github.com/AtomGraph/LinkedDataHub + + + + UTF-8 + 21 + + + + + org.apache.jena + jena-arq + 6.1.0 + + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient-cache + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpclient-osgi + + + org.apache.httpcomponents + httpcore-osgi + + + + + org.junit.jupiter + junit-jupiter + 5.12.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + 21 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + + org.codehaus.mojo + versions-maven-plugin + 2.21.0 + + + + + diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Acl.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Acl.java new file mode 100644 index 000000000..e95f18677 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Acl.java @@ -0,0 +1,120 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.ACL; +import com.atomgraph.linkeddatahub.rdf.vocabulary.DH; +import java.net.URI; +import java.util.List; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.sparql.vocabulary.FOAF; +import org.apache.jena.vocabulary.DCTerms; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; + +/** + * Access control: authorizations and the groups they are granted to. + *

+ * Both live in their own document, so each model carries the dh:Item wrapper + * around the resource it is about - the document is not the resource. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Acl +{ + + private Acl() { } + + /** + * Builds an authorization document. Agents, agent classes and agent groups say who; the + * accessTo and accessToClass lists say what; the modes say how. + * + * @param doc document URI + * @param uri authorization URI, relative or absolute (optional, blank node when absent) + * @param label authorization label, also the document title + * @param comment authorization comment (optional) + * @param agents WebIDs the authorization is granted to + * @param agentClasses agent classes the authorization is granted to + * @param agentGroups agent groups the authorization is granted to + * @param to URIs of the documents access is granted on + * @param toAllIn URIs of the classes whose instances access is granted on + * @param modes access modes granted + * @return authorization model + */ + public static Model authorization(URI doc, String uri, String label, String comment, + List agents, List agentClasses, List agentGroups, + List to, List toAllIn, List modes) + { + Model model = ModelFactory.createDefaultModel(); + + Resource auth = Subjects.of(model, doc, uri). + addProperty(RDF.type, ACL.Authorization). + addProperty(RDFS.label, label); + if (comment != null) auth.addProperty(RDFS.comment, comment); + + model.createResource(doc.toString()). + addProperty(RDF.type, DH.Item). + addProperty(FOAF.primaryTopic, auth). + addProperty(DCTerms.title, label); + + addResourceValues(auth, ACL.agent, agents); + addResourceValues(auth, ACL.agentClass, agentClasses); + addResourceValues(auth, ACL.agentGroup, agentGroups); + addResourceValues(auth, ACL.accessTo, to); + addResourceValues(auth, ACL.accessToClass, toAllIn); + modes.forEach(mode -> auth.addProperty(ACL.mode, mode)); + + return model; + } + + /** + * Builds an agent group document. + * + * @param doc document URI + * @param uri group URI, relative or absolute (optional, blank node when absent) + * @param name group name, also the document title + * @param description group description (optional) + * @param members WebIDs of the group's members + * @return group model + */ + public static Model group(URI doc, String uri, String name, String description, List members) + { + Model model = ModelFactory.createDefaultModel(); + + Resource group = Subjects.of(model, doc, uri). + addProperty(RDF.type, FOAF.Group). + addProperty(FOAF.name, name); + if (description != null) group.addProperty(DCTerms.description, description); + members.forEach(member -> group.addProperty(FOAF.member, model.createResource(member.toString()))); + + model.createResource(doc.toString()). + addProperty(RDF.type, DH.Item). + addProperty(FOAF.primaryTopic, group). + addProperty(DCTerms.title, name); + + return model; + } + + private static void addResourceValues(Resource subject, Property property, List values) + { + values.forEach(value -> subject.addProperty(property, subject.getModel().createResource(value.toString()))); + } + +} diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Blocks.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Blocks.java new file mode 100644 index 000000000..9cf87a4a7 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Blocks.java @@ -0,0 +1,96 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.AC; +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; +import java.net.URI; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.vocabulary.DCTerms; +import org.apache.jena.vocabulary.RDF; + +/** + * Content blocks, the ordered body of a document. + *

+ * A block is attached to its document by a container membership property + * (rdf:_1, rdf:_2, …) which fixes its position; see + * {@link SequenceNumbers} for reading the next free one off an existing document. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Blocks +{ + + private Blocks() { } + + /** + * Builds an object block: a block whose value is another resource, rendered by the mode given. + * + * @param target target document URI + * @param seq container membership property fixing the block's position + * @param uri block URI, relative or absolute (optional, blank node when absent) + * @param value URI of the resource the block shows + * @param title block title (optional) + * @param description block description (optional) + * @param mode layout mode URI (optional) + * @return block model + */ + public static Model object(URI target, Property seq, String uri, URI value, String title, String description, URI mode) + { + Model model = ModelFactory.createDefaultModel(); + + Resource block = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.Object). + addProperty(RDF.value, model.createResource(value.toString())); + model.createResource(target.toString()).addProperty(seq, block); + if (title != null) block.addProperty(DCTerms.title, title); + if (description != null) block.addProperty(DCTerms.description, description); + if (mode != null) block.addProperty(AC.mode, model.createResource(mode.toString())); + + return model; + } + + /** + * Builds an XHTML block: a block whose value is markup carried as an + * rdf:XMLLiteral. + * + * @param target target document URI + * @param seq container membership property fixing the block's position + * @param uri block URI, relative or absolute (optional, blank node when absent) + * @param value XHTML markup + * @param title block title (optional) + * @param description block description (optional) + * @return block model + */ + public static Model xhtml(URI target, Property seq, String uri, String value, String title, String description) + { + Model model = ModelFactory.createDefaultModel(); + + Resource block = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.XHTML). + addProperty(RDF.value, model.createTypedLiteral(value, RDF.dtXMLLiteral)); + model.createResource(target.toString()).addProperty(seq, block); + if (title != null) block.addProperty(DCTerms.title, title); + if (description != null) block.addProperty(DCTerms.description, description); + + return model; + } + +} diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/Digests.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Digests.java similarity index 97% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/Digests.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Digests.java index dd1638251..e0d02435f 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/Digests.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Digests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.util; +package com.atomgraph.linkeddatahub.rdf; import java.io.IOException; import java.io.InputStream; diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Documents.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Documents.java new file mode 100644 index 000000000..5912db4f8 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Documents.java @@ -0,0 +1,105 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.AC; +import com.atomgraph.linkeddatahub.rdf.vocabulary.DH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SPIN; +import java.net.URI; +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.sparql.vocabulary.FOAF; +import org.apache.jena.vocabulary.DCTerms; +import org.apache.jena.vocabulary.RDF; + +/** + * The two document kinds of the hierarchy: containers and items. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Documents +{ + + private Documents() { } + + /** + * Builds the container document model with its first content block: the given block URI, + * a children view with an explicit mode, or the default children view. + * + * @param doc document URI + * @param title document title + * @param description document description (optional) + * @param block content block URI (optional) + * @param mode children view mode URI (optional, ignored when block is given) + * @param primaryTopic URI of the document's primary topic, relative or absolute (optional) + * @return document model + */ + public static Model container(URI doc, String title, String description, URI block, URI mode, String primaryTopic) + { + Model model = ModelFactory.createDefaultModel(); + + Resource container = model.createResource(doc.toString()). + addProperty(RDF.type, DH.Container). + addProperty(DCTerms.title, title); + + if (block != null) container.addProperty(RDF.li(1), model.createResource(block.toString())); + else if (mode != null) container.addProperty(RDF.li(1), model.createResource(). + addProperty(RDF.type, LDH.Object). + addProperty(RDF.value, model.createResource(). + addProperty(RDF.type, LDH.View). + addProperty(SPIN.query, LDH.SelectChildren). + addProperty(AC.mode, model.createResource(mode.toString())))); + else container.addProperty(RDF.li(1), model.createResource(). + addProperty(RDF.type, LDH.Object). + addProperty(RDF.value, LDH.ChildrenView)); + + if (description != null) container.addProperty(DCTerms.description, description); + // See item(): resolved against the document, and singular by the vocabulary. + if (primaryTopic != null) container.addProperty(FOAF.primaryTopic, model.createResource(doc.resolve(primaryTopic).toString())); + + return model; + } + + /** + * Builds the item document model. + * + * @param doc document URI + * @param title document title + * @param description document description (optional) + * @param primaryTopic URI of the document's primary topic, relative or absolute (optional) + * @return document model + */ + public static Model item(URI doc, String title, String description, String primaryTopic) + { + Model model = ModelFactory.createDefaultModel(); + + Resource item = model.createResource(doc.toString()). + addProperty(RDF.type, DH.Item). + addProperty(DCTerms.title, title); + if (description != null) item.addProperty(DCTerms.description, description); + // Resolved against the document, so the conventional fragment topic is "#this" and a + // document about something described elsewhere takes that resource's absolute URI. + // Singular because foaf:primaryTopic is an owl:FunctionalProperty: a second value would + // not mean a second topic, it would entail the two topics are the same resource. + if (primaryTopic != null) item.addProperty(FOAF.primaryTopic, model.createResource(doc.resolve(primaryTopic).toString())); + + return model; + } + +} diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Imports.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Imports.java new file mode 100644 index 000000000..5478b6cd9 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Imports.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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SD; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SPIN; +import java.net.URI; +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.apache.jena.vocabulary.RDF; + +/** + * Import descriptions - a file plus the transformation that turns it into RDF. + *

+ * Creating one only describes the import; the platform runs it asynchronously once the + * description lands. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Imports +{ + + private Imports() { } + + /** + * Builds a CSV import description: a CSV file, the delimiter that parses it, and a + * CONSTRUCT query mapping each row to triples. + * + * @param target target document URI + * @param uri import URI, relative or absolute (optional, blank node when absent) + * @param title import title + * @param query URI of the query mapping rows to triples + * @param file URI of the CSV file + * @param delimiter CSV delimiter character + * @param description import description (optional) + * @return import model + */ + public static Model csv(URI target, String uri, String title, URI query, URI file, String delimiter, String description) + { + Model model = ModelFactory.createDefaultModel(); + + Resource csvImport = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.CSVImport). + addProperty(DCTerms.title, title). + addProperty(SPIN.query, model.createResource(query.toString())). + addProperty(LDH.file, model.createResource(file.toString())). + addProperty(LDH.delimiter, delimiter); + if (description != null) csvImport.addProperty(DCTerms.description, description); + + return model; + } + + /** + * Builds an RDF import description: an RDF file, optionally transformed by a query and + * optionally loaded into a named graph. + * + * @param target target document URI + * @param uri import URI, relative or absolute (optional, blank node when absent) + * @param title import title + * @param file URI of the RDF file + * @param query URI of the query transforming the parsed graph (optional) + * @param graph name of the graph the triples load into (optional) + * @param description import description (optional) + * @return import model + */ + public static Model rdf(URI target, String uri, String title, URI file, URI query, URI graph, String description) + { + Model model = ModelFactory.createDefaultModel(); + + Resource rdfImport = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.RDFImport). + addProperty(DCTerms.title, title). + addProperty(LDH.file, model.createResource(file.toString())); + if (graph != null) rdfImport.addProperty(SD.name, model.createResource(graph.toString())); + if (query != null) rdfImport.addProperty(SPIN.query, model.createResource(query.toString())); + if (description != null) rdfImport.addProperty(DCTerms.description, description); + + return model; + } + +} diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Ontologies.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Ontologies.java new file mode 100644 index 000000000..9f98faae9 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Ontologies.java @@ -0,0 +1,199 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.DH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SPIN; +import java.net.URI; +import java.util.List; +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.sparql.vocabulary.FOAF; +import org.apache.jena.vocabulary.DCTerms; +import org.apache.jena.vocabulary.OWL; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.RDFS; + +/** + * The terms an application ontology is edited in: the ontology document itself, its classes, + * their restrictions, and the constructors and constraints attached to them. + *

+ * These carry rdfs:label/rdfs:comment rather than + * dct:title/dct:description - ontology terms are annotated the way + * RDFS annotates them, not the way documents are described. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Ontologies +{ + + private Ontologies() { } + + /** + * Builds an ontology document. + * + * @param doc document URI + * @param uri ontology URI, relative or absolute (optional, blank node when absent) + * @param label ontology label, also the document title + * @param comment ontology comment (optional) + * @return ontology model + */ + public static Model ontology(URI doc, String uri, String label, String comment) + { + Model model = ModelFactory.createDefaultModel(); + + Resource ontology = Subjects.of(model, doc, uri). + addProperty(RDF.type, OWL.Ontology). + addProperty(RDFS.label, label); + if (comment != null) ontology.addProperty(RDFS.comment, comment); + + model.createResource(doc.toString()). + addProperty(RDF.type, DH.Item). + addProperty(FOAF.primaryTopic, ontology). + addProperty(DCTerms.title, label); + + return model; + } + + /** + * Builds a class, with its constructor, constraint and superclasses. + * + * @param target target ontology document URI + * @param uri class URI, relative or absolute (optional, blank node when absent) + * @param label class label + * @param comment class comment (optional) + * @param constructor URI of the query constructing instances of the class (optional) + * @param constraint URI of the class's constraint (optional) + * @param superClasses URIs of the class's superclasses + * @return class model + */ + public static Model owlClass(URI target, String uri, String label, String comment, URI constructor, URI constraint, List superClasses) + { + Model model = ModelFactory.createDefaultModel(); + + Resource cls = Subjects.of(model, target, uri). + addProperty(RDF.type, OWL.Class). + addProperty(RDFS.label, label); + if (comment != null) cls.addProperty(RDFS.comment, comment); + if (constructor != null) cls.addProperty(SPIN.constructor, model.createResource(constructor.toString())); + if (constraint != null) cls.addProperty(SPIN.constraint, model.createResource(constraint.toString())); + superClasses.forEach(superClass -> cls.addProperty(RDFS.subClassOf, model.createResource(superClass.toString()))); + + return model; + } + + /** + * Builds a property restriction. + * + * @param target target ontology document URI + * @param uri restriction URI, relative or absolute (optional, blank node when absent) + * @param label restriction label + * @param comment restriction comment (optional) + * @param onProperty URI of the restricted property (optional) + * @param allValuesFrom URI of the class the property's values must come from (optional) + * @param hasValue URI of the value the property must have (optional) + * @return restriction model + */ + public static Model restriction(URI target, String uri, String label, String comment, URI onProperty, URI allValuesFrom, URI hasValue) + { + Model model = ModelFactory.createDefaultModel(); + + Resource restriction = Subjects.of(model, target, uri). + addProperty(RDF.type, OWL.Restriction). + addProperty(RDFS.label, label); + if (comment != null) restriction.addProperty(RDFS.comment, comment); + if (onProperty != null) restriction.addProperty(OWL.onProperty, model.createResource(onProperty.toString())); + if (allValuesFrom != null) restriction.addProperty(OWL.allValuesFrom, model.createResource(allValuesFrom.toString())); + if (hasValue != null) restriction.addProperty(OWL.hasValue, model.createResource(hasValue.toString())); + + return model; + } + + /** + * Builds a SPIN constructor query - the query that builds a new instance of a class. + *

+ * The same shape as {@link Queries#query}, annotated for an ontology rather than described + * as a document. + * + * @param target target ontology document URI + * @param uri query URI, relative or absolute (optional, blank node when absent) + * @param queryType query form, e.g. {@link SP#Construct} + * @param label query label + * @param queryText the SPARQL query string + * @param service URI of the SPARQL service the query runs against (optional) + * @param comment query comment (optional) + * @return constructor model + */ + public static Model constructor(URI target, String uri, Resource queryType, String label, String queryText, URI service, String comment) + { + Model model = ModelFactory.createDefaultModel(); + + Resource query = Subjects.of(model, target, uri). + addProperty(RDF.type, queryType). + addProperty(RDFS.label, label). + addProperty(SP.text, queryText); + if (comment != null) query.addProperty(RDFS.comment, comment); + if (service != null) query.addProperty(LDH.service, model.createResource(service.toString())); + + return model; + } + + /** + * Builds a constraint requiring a property to have a value. + * + * @param target target ontology document URI + * @param uri constraint URI, relative or absolute (optional, blank node when absent) + * @param label constraint label + * @param property URI of the property that must have a value + * @param comment constraint comment (optional) + * @return constraint model + */ + public static Model propertyConstraint(URI target, String uri, String label, URI property, String comment) + { + Model model = ModelFactory.createDefaultModel(); + + Resource constraint = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.MissingPropertyValue). + addProperty(RDFS.label, label). + addProperty(SP.arg1, model.createResource(property.toString())); + if (comment != null) constraint.addProperty(RDFS.comment, comment); + + return model; + } + + /** + * Builds the annotation naming an imported ontology's source, written into the graph the + * import landed in. + * + * @param graph URI of the graph the ontology was imported into + * @param source URI the ontology was imported from + * @return annotation model + */ + public static Model annotation(URI graph, URI source) + { + Model model = ModelFactory.createDefaultModel(); + + model.createResource(graph.toString()). + addProperty(FOAF.primaryTopic, model.createResource(source.toString())); + + return model; + } + +} diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java new file mode 100644 index 000000000..5840f4e50 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java @@ -0,0 +1,66 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; +import java.net.URI; +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.apache.jena.vocabulary.RDF; + +/** + * Stored SPARQL queries. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Queries +{ + + private Queries() { } + + /** + * Builds a stored query. The query form is the caller's to state - {@link SP#Select}, + * {@link SP#Construct} and {@link SP#Describe} are all stored the same way, and only the + * type says which one this is. + * + * @param target target document URI + * @param uri query URI, relative or absolute (optional, blank node when absent) + * @param queryType query form, e.g. {@link SP#Select} + * @param title query title + * @param queryText the SPARQL query string + * @param service URI of the SPARQL service the query runs against (optional) + * @param description query description (optional) + * @return query model + */ + public static Model query(URI target, String uri, Resource queryType, String title, String queryText, URI service, String description) + { + Model model = ModelFactory.createDefaultModel(); + + Resource query = Subjects.of(model, target, uri). + addProperty(RDF.type, queryType). + addProperty(DCTerms.title, title). + addProperty(SP.text, queryText); + if (service != null) query.addProperty(LDH.service, model.createResource(service.toString())); + if (description != null) query.addProperty(DCTerms.description, description); + + return model; + } + +} diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/SequenceNumbers.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/SequenceNumbers.java similarity index 98% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/SequenceNumbers.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/SequenceNumbers.java index 22fcc3738..8da89aed4 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/SequenceNumbers.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/SequenceNumbers.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.util; +package com.atomgraph.linkeddatahub.rdf; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Property; diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Services.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Services.java new file mode 100644 index 000000000..f7f496a2d --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Services.java @@ -0,0 +1,69 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.A; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SD; +import java.net.URI; +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.apache.jena.vocabulary.RDF; + +/** + * SPARQL service descriptions - the endpoints a dataspace can query and write. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Services +{ + + private Services() { } + + /** + * Builds a generic SPARQL service description. + * + * @param target target document URI + * @param uri service URI, relative or absolute (optional, blank node when absent) + * @param title service title + * @param endpoint SPARQL endpoint URI + * @param graphStore Graph Store Protocol endpoint URI (optional) + * @param authUser HTTP Basic auth user name (optional) + * @param authPwd HTTP Basic auth password (optional) + * @param description service description (optional) + * @return service model + */ + public static Model service(URI target, String uri, String title, URI endpoint, URI graphStore, String authUser, String authPwd, String description) + { + Model model = ModelFactory.createDefaultModel(); + + Resource service = Subjects.of(model, target, uri). + addProperty(RDF.type, SD.Service). + addProperty(DCTerms.title, title). + addProperty(SD.endpoint, model.createResource(endpoint.toString())). + addProperty(SD.supportedLanguage, SD.SPARQL11Query). + addProperty(SD.supportedLanguage, SD.SPARQL11Update); + if (graphStore != null) service.addProperty(A.graphStore, model.createResource(graphStore.toString())); + if (authUser != null) service.addProperty(A.authUser, authUser); + if (authPwd != null) service.addProperty(A.authPwd, authPwd); + if (description != null) service.addProperty(DCTerms.description, description); + + return model; + } + +} diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/Slugs.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Slugs.java similarity index 96% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/Slugs.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Slugs.java index 6d77b04ed..3f5b49d56 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/util/Slugs.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Slugs.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.util; +package com.atomgraph.linkeddatahub.rdf; import java.util.Locale; import java.util.UUID; diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Subjects.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Subjects.java new file mode 100644 index 000000000..fd62b6686 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Subjects.java @@ -0,0 +1,51 @@ +/* + * 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.rdf; + +import java.net.URI; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Resource; + +/** + * The subject a description appended to a document is about. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Subjects +{ + + private Subjects() { } + + /** + * Returns the subject resource for an appended description: the given URI resolved against + * the target document URI, or a fresh blank node when no URI is given. + *

+ * A blank node is the right default because most appended descriptions - a content block, a + * query, a chart - are only ever referred to from within the document that carries them, so + * minting a URI for them would add an identifier nothing dereferences. + * + * @param model model to create the resource in + * @param target target document URI + * @param uri subject URI, absolute or relative to the target, or null for a blank node + * @return subject resource + */ + public static Resource of(Model model, URI target, String uri) + { + return uri != null ? model.createResource(target.resolve(uri).toString()) : model.createResource(); + } + +} diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/URIs.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/URIs.java new file mode 100644 index 000000000..915828cc1 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/URIs.java @@ -0,0 +1,87 @@ +/* + * 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.rdf; + +import java.net.URI; +import java.nio.charset.StandardCharsets; + +/** + * Where LinkedDataHub puts things: how a child document's URI is derived from its parent and a + * slug, and how an end-user application's base URI relates to its admin application's. + *

+ * Conventions, not manipulation - every method here encodes a rule the platform also applies, so + * a client that follows them addresses documents the platform will agree exist. + * + * @author Martynas Jusevičius {@literal } + */ +public final class URIs +{ + + private URIs() { } + + /** + * Builds the URI of a child document from the parent container URI and a path segment slug. + *

+ * The trailing slash is what makes it a container-addressable document rather than a fragment + * of its parent, so it is added here rather than left to the caller. + * + * @param parent parent container URI (with trailing slash) + * @param slug path segment + * @return child document URI (with trailing slash) + */ + public static URI childURI(URI parent, String slug) + { + return URI.create(parent.toString() + encodeSlug(slug) + "/"); + } + + /** + * Percent-encodes a string as a URI path segment. All characters except RFC 3986 + * unreserved ones are encoded, including / - a slug names one segment, so a + * slash in it is data rather than a further step in the path. + * + * @param slug path segment + * @return encoded path segment + */ + public static String encodeSlug(String slug) + { + StringBuilder sb = new StringBuilder(); + + for (byte b : slug.getBytes(StandardCharsets.UTF_8)) + { + char c = (char)(b & 0xFF); + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '-' || c == '.' || c == '_' || c == '~') sb.append(c); + else sb.append('%').append(String.format("%02X", b & 0xFF)); + } + + return sb.toString(); + } + + /** + * Converts an end-user application base URI to the base URI of its admin application + * by prefixing the host with the admin. subdomain, which is how nginx's + * wildcard routing tells the two apart. + * + * @param base end-user base URI + * @return admin base URI + */ + public static URI adminBase(URI base) + { + return URI.create(base.toString().replaceFirst("://", "://admin.")); + } + +} diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/sparql/Updates.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Updates.java similarity index 99% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/sparql/Updates.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Updates.java index 160cb6bb6..02cbe6251 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/sparql/Updates.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Updates.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.sparql; +package com.atomgraph.linkeddatahub.rdf; import java.net.URI; import org.apache.jena.query.ParameterizedSparqlString; diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Views.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Views.java new file mode 100644 index 000000000..74fa2b5c1 --- /dev/null +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Views.java @@ -0,0 +1,94 @@ +/* + * 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.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.AC; +import com.atomgraph.linkeddatahub.rdf.vocabulary.LDH; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SPIN; +import java.net.URI; +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.apache.jena.vocabulary.RDF; + +/** + * Views: a query plus how its results are shown. + * + * @author Martynas Jusevičius {@literal } + */ +public final class Views +{ + + private Views() { } + + /** + * Builds a view of a query's results. + * + * @param target target document URI + * @param uri view URI, relative or absolute (optional, blank node when absent) + * @param query URI of the query the view runs + * @param title view title (optional) + * @param description view description (optional) + * @param mode layout mode URI (optional) + * @return view model + */ + public static Model view(URI target, String uri, URI query, String title, String description, URI mode) + { + Model model = ModelFactory.createDefaultModel(); + + Resource view = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.View). + addProperty(SPIN.query, model.createResource(query.toString())); + if (title != null) view.addProperty(DCTerms.title, title); + if (description != null) view.addProperty(DCTerms.description, description); + if (mode != null) view.addProperty(AC.mode, model.createResource(mode.toString())); + + return model; + } + + /** + * Builds a chart of a SELECT query's result set. The category and series variable names + * pick the result-set columns the chart's axes read. + * + * @param target target document URI + * @param uri chart URI, relative or absolute (optional, blank node when absent) + * @param title chart title + * @param query URI of the query the chart runs + * @param chartType chart type URI + * @param categoryVarName result-set variable name providing the categories + * @param seriesVarName result-set variable name providing the series + * @param description chart description (optional) + * @return chart model + */ + public static Model resultSetChart(URI target, String uri, String title, URI query, URI chartType, String categoryVarName, String seriesVarName, String description) + { + Model model = ModelFactory.createDefaultModel(); + + Resource chart = Subjects.of(model, target, uri). + addProperty(RDF.type, LDH.ResultSetChart). + addProperty(DCTerms.title, title). + addProperty(SPIN.query, model.createResource(query.toString())). + addProperty(LDH.chartType, model.createResource(chartType.toString())). + addProperty(LDH.categoryVarName, categoryVarName). + addProperty(LDH.seriesVarName, seriesVarName); + if (description != null) chart.addProperty(DCTerms.description, description); + + return model; + } + +} diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/A.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/A.java similarity index 96% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/A.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/A.java index 73dd5abd3..c37362ccb 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/A.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/A.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.ResourceFactory; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/AC.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/AC.java similarity index 95% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/AC.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/AC.java index 9de56512e..d04870afe 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/AC.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/AC.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.ResourceFactory; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/ACL.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/ACL.java similarity index 97% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/ACL.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/ACL.java index e7f3ff709..b193ce1ba 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/ACL.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/ACL.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/DH.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/DH.java similarity index 96% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/DH.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/DH.java index dcc80f803..92517f2b5 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/DH.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/DH.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/LDH.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/LDH.java similarity index 98% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/LDH.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/LDH.java index 397f57f70..c4f2e0637 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/LDH.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/LDH.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/NFO.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/NFO.java similarity index 96% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/NFO.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/NFO.java index de6a53c65..b388abc2f 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/NFO.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/NFO.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SD.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SD.java similarity index 97% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SD.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SD.java index 4bec9d26f..47eaf2184 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SD.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SD.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SP.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SP.java similarity index 96% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SP.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SP.java index 6634441f9..8217816c8 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SP.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SP.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; diff --git a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SPIN.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SPIN.java similarity index 96% rename from cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SPIN.java rename to rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SPIN.java index a4cbec33d..9c183d737 100644 --- a/cli/src/main/java/com/atomgraph/linkeddatahub/cli/vocab/SPIN.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/vocabulary/SPIN.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.vocab; +package com.atomgraph.linkeddatahub.rdf.vocabulary; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.ResourceFactory; diff --git a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/command/ModelBuildersTest.java b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/ModelBuildersTest.java similarity index 80% rename from cli/src/test/java/com/atomgraph/linkeddatahub/cli/command/ModelBuildersTest.java rename to rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/ModelBuildersTest.java index 770dd454c..88686d467 100644 --- a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/command/ModelBuildersTest.java +++ b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/ModelBuildersTest.java @@ -14,22 +14,10 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.command; - -import com.atomgraph.linkeddatahub.cli.command.admin.acl.CreateAuthorization; -import com.atomgraph.linkeddatahub.cli.command.admin.acl.CreateGroup; -import com.atomgraph.linkeddatahub.cli.command.admin.ontologies.AddClass; -import com.atomgraph.linkeddatahub.cli.command.admin.ontologies.AddConstructor; -import com.atomgraph.linkeddatahub.cli.command.admin.ontologies.AddPropertyConstraint; -import com.atomgraph.linkeddatahub.cli.command.admin.ontologies.AddRestriction; -import com.atomgraph.linkeddatahub.cli.command.admin.ontologies.CreateOntology; -import com.atomgraph.linkeddatahub.cli.command.admin.ontologies.ImportOntology; -import com.atomgraph.linkeddatahub.cli.command.content.AddObjectBlock; -import com.atomgraph.linkeddatahub.cli.command.content.AddXHTMLBlock; -import com.atomgraph.linkeddatahub.cli.command.imports.AddCSVImport; -import com.atomgraph.linkeddatahub.cli.command.imports.AddRDFImport; -import com.atomgraph.linkeddatahub.cli.vocab.ACL; -import com.atomgraph.linkeddatahub.cli.vocab.SP; +package com.atomgraph.linkeddatahub.rdf; + +import com.atomgraph.linkeddatahub.rdf.vocabulary.ACL; +import com.atomgraph.linkeddatahub.rdf.vocabulary.SP; import java.io.StringReader; import java.net.URI; import java.util.List; @@ -88,7 +76,7 @@ public void createItem() dct:title "My item" ; dct:description "Desc" . """), - CreateItem.buildModel(doc, "My item", "Desc", null)); + Documents.item(doc, "My item", "Desc", null)); } @Test @@ -103,7 +91,7 @@ public void createItemWithFragmentPrimaryTopic() dct:title "My item" ; foaf:primaryTopic . """), - CreateItem.buildModel(doc, "My item", null, "#this")); + Documents.item(doc, "My item", null, "#this")); } @Test @@ -118,7 +106,7 @@ public void createItemWithAbsolutePrimaryTopic() dct:title "About Bob" ; foaf:primaryTopic . """), - CreateItem.buildModel(doc, "About Bob", null, "https://example.org/bob#me")); + Documents.item(doc, "About Bob", null, "https://example.org/bob#me")); } @Test @@ -129,7 +117,7 @@ public void createContainerDefaultChildrenView() dct:title "Some" ; rdf:_1 [ a ldh:Object ; rdf:value ldh:ChildrenView ] . """), - CreateContainer.buildModel(TARGET, "Some", null, null, null, null)); + Documents.container(TARGET, "Some", null, null, null, null)); } @Test @@ -140,7 +128,7 @@ public void createContainerWithMode() dct:title "Some" ; rdf:_1 [ a ldh:Object ; rdf:value [ a ldh:View ; spin:query ldh:SelectChildren ; ac:mode ] ] . """), - CreateContainer.buildModel(TARGET, "Some", null, null, URI.create("https://w3id.org/atomgraph/client#GridMode"), null)); + Documents.container(TARGET, "Some", null, null, URI.create("https://w3id.org/atomgraph/client#GridMode"), null)); } @Test @@ -151,7 +139,7 @@ public void createContainerWithBlock() dct:title "Some" ; rdf:_1 . """), - CreateContainer.buildModel(TARGET, "Some", null, URI.create("https://localhost:4443/some/#block"), null, null)); + Documents.container(TARGET, "Some", null, URI.create("https://localhost:4443/some/#block"), null, null)); } @Test @@ -165,7 +153,7 @@ public void createContainerWithPrimaryTopic() rdf:_1 [ a ldh:Object ; rdf:value ldh:ChildrenView ] ; foaf:primaryTopic <#this> . """), - CreateContainer.buildModel(TARGET, "Some", null, null, null, "#this")); + Documents.container(TARGET, "Some", null, null, null, "#this")); } @Test @@ -177,7 +165,7 @@ public void addViewWithURIAndMode() dct:title "View" ; ac:mode . """), - AddView.buildModel(TARGET, "#view", URI.create("https://localhost:4443/queries/q/#this"), "View", null, + Views.view(TARGET, "#view", URI.create("https://localhost:4443/queries/q/#this"), "View", null, URI.create("https://w3id.org/atomgraph/client#GridMode"))); } @@ -190,7 +178,7 @@ public void addConstructQuery() sp:text \"""CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }\""" ; ldh:service . """), - AddConstruct.buildModel(TARGET, null, SP.Construct, "Query", "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", + Queries.query(TARGET, null, SP.Construct, "Query", "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", URI.create("https://localhost:4443/services/s/#this"), null)); } @@ -208,7 +196,7 @@ public void createOntology() foaf:primaryTopic _:ontology ; dct:title "My ontology" . """), - CreateOntology.buildModel(doc, null, "My ontology", "Comment")); + Ontologies.ontology(doc, null, "My ontology", "Comment")); } @Test @@ -225,7 +213,7 @@ public void createGroup() foaf:primaryTopic _:group ; dct:title "Editors" . """), - CreateGroup.buildModel(doc, null, "Editors", null, + Acl.group(doc, null, "Editors", null, List.of(URI.create("https://localhost:4443/acl/agents/a/#this"), URI.create("https://localhost:4443/acl/agents/b/#this")))); } @@ -245,7 +233,7 @@ public void createAuthorization() foaf:primaryTopic _:auth ; dct:title "Auth" . """), - CreateAuthorization.buildModel(doc, null, "Auth", null, + Acl.authorization(doc, null, "Auth", null, List.of(URI.create("https://localhost:4443/acl/agents/a/#this")), List.of(), List.of(), List.of(URI.create("https://localhost:4443/some/")), List.of(), List.of(ACL.Read, ACL.Write))); @@ -261,7 +249,7 @@ public void addCSVImport() ldh:file ; ldh:delimiter "," . """), - AddCSVImport.buildModel(TARGET, null, "Cities", URI.create("https://localhost:4443/some/#query"), + Imports.csv(TARGET, null, "Cities", URI.create("https://localhost:4443/some/#query"), URI.create("https://localhost:4443/uploads/abc"), ",", null)); } @@ -274,7 +262,7 @@ public void addRDFImportWithGraph() ldh:file ; sd:name . """), - AddRDFImport.buildModel(TARGET, "#import", "Data", URI.create("https://localhost:4443/uploads/abc"), + Imports.rdf(TARGET, "#import", "Data", URI.create("https://localhost:4443/uploads/abc"), null, URI.create("https://localhost:4443/graphs/g/"), null)); } @@ -286,7 +274,7 @@ public void addXHTMLBlock() _:block a ldh:XHTML ; rdf:value "

Hello

"^^rdf:XMLLiteral . """), - AddXHTMLBlock.buildModel(TARGET, RDF.li(4), null, "

Hello

", null, null)); + Blocks.xhtml(TARGET, RDF.li(4), null, "

Hello

", null, null)); } @Test @@ -301,7 +289,7 @@ public void addGenericServiceWithGraphStoreAndAuth() a:authUser "user" ; a:authPwd "pwd" . """), - AddGenericService.buildModel(TARGET, "#service", "Remote", URI.create("https://remote.example/sparql"), + Services.service(TARGET, "#service", "Remote", URI.create("https://remote.example/sparql"), URI.create("https://remote.example/service"), "user", "pwd", null)); } @@ -314,7 +302,7 @@ public void addGenericServiceMinimal() sd:endpoint ; sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update . """), - AddGenericService.buildModel(TARGET, "#service", "Remote", URI.create("https://remote.example/sparql"), + Services.service(TARGET, "#service", "Remote", URI.create("https://remote.example/sparql"), null, null, null, null)); } @@ -330,7 +318,7 @@ public void addResultSetChart() ldh:categoryVarName "category" ; ldh:seriesVarName "series" . """), - AddResultSetChart.buildModel(TARGET, "#chart", "Chart", URI.create("https://localhost:4443/queries/select/#this"), + Views.resultSetChart(TARGET, "#chart", "Chart", URI.create("https://localhost:4443/queries/select/#this"), URI.create("https://w3id.org/atomgraph/client#BarChart"), "category", "series", "Desc")); } @@ -345,7 +333,7 @@ public void addClassWithSuperClasses() spin:constraint ; rdfs:subClassOf , . """), - AddClass.buildModel(TARGET, "#Concept", "Concept", "A concept", + Ontologies.owlClass(TARGET, "#Concept", "Concept", "A concept", URI.create("https://localhost:4443/queries/construct/#this"), URI.create("https://localhost:4443/constraints/#this"), List.of(URI.create("https://localhost:4443/ns#Thing"), URI.create("https://localhost:4443/ns#Other")))); @@ -358,7 +346,7 @@ public void addClassMinimal() <#Concept> a owl:Class ; rdfs:label "Concept" . """), - AddClass.buildModel(TARGET, "#Concept", "Concept", null, null, null, List.of())); + Ontologies.owlClass(TARGET, "#Concept", "Concept", null, null, null, List.of())); } @Test @@ -371,7 +359,7 @@ public void addConstructor() sp:text "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }" ; ldh:service . """), - AddConstructor.buildModel(TARGET, "#constructor", SP.Construct, "Constructor", + Ontologies.constructor(TARGET, "#constructor", SP.Construct, "Constructor", "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }", URI.create("https://localhost:4443/services/remote/#this"), "Builds a Concept")); } @@ -384,7 +372,7 @@ public void addPropertyConstraint() rdfs:label "Title required" ; sp:arg1 dct:title . """), - AddPropertyConstraint.buildModel(TARGET, "#constraint", "Title required", + Ontologies.propertyConstraint(TARGET, "#constraint", "Title required", URI.create("http://purl.org/dc/terms/title"), null)); } @@ -399,7 +387,7 @@ public void addRestriction() owl:allValuesFrom rdfs:Literal ; owl:hasValue . """), - AddRestriction.buildModel(TARGET, "#restriction", "Has title", "Every instance carries a title", + Ontologies.restriction(TARGET, "#restriction", "Has title", "Every instance carries a title", URI.create("http://purl.org/dc/terms/title"), URI.create("http://www.w3.org/2000/01/rdf-schema#Literal"), URI.create("https://localhost:4443/values/default/"))); @@ -416,7 +404,7 @@ public void addObjectBlock() dct:description "Desc" ; ac:mode . """), - AddObjectBlock.buildModel(TARGET, RDF.li(2), null, URI.create("https://localhost:4443/other/"), + Blocks.object(TARGET, RDF.li(2), null, URI.create("https://localhost:4443/other/"), "Block", "Desc", URI.create("https://w3id.org/atomgraph/client#ReadMode"))); } @@ -430,7 +418,7 @@ public void importOntologyAnnotation() assertIsomorphic(parse(""" <> foaf:primaryTopic . """), - ImportOntology.buildAnnotationModel(TARGET, URI.create("http://www.w3.org/2004/02/skos/core#"))); + Ontologies.annotation(TARGET, URI.create("http://www.w3.org/2004/02/skos/core#"))); } } diff --git a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/SequenceNumbersTest.java b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/SequenceNumbersTest.java similarity index 98% rename from cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/SequenceNumbersTest.java rename to rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/SequenceNumbersTest.java index 275030c71..2f50d5994 100644 --- a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/util/SequenceNumbersTest.java +++ b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/SequenceNumbersTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.util; +package com.atomgraph.linkeddatahub.rdf; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; diff --git a/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/URIsTest.java b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/URIsTest.java new file mode 100644 index 000000000..bd908e58f --- /dev/null +++ b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/URIsTest.java @@ -0,0 +1,54 @@ +/* + * 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.rdf; + +import java.net.URI; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for {@link URIs}. + */ +public class URIsTest +{ + + @Test + public void adminBasePrefixesHostWithAdminSubdomain() + { + assertEquals(URI.create("https://admin.localhost:4443/"), URIs.adminBase(URI.create("https://localhost:4443/"))); + } + + @Test + public void encodeSlugKeepsUnreservedCharacters() + { + assertEquals("abc-._~123", URIs.encodeSlug("abc-._~123")); + } + + @Test + public void encodeSlugEncodesReservedAndNonASCII() + { + assertEquals("a%20b%2F%C4%87", URIs.encodeSlug("a b/ć")); + } + + @Test + public void childURIAppendsEncodedSlugAndSlash() + { + assertEquals(URI.create("https://localhost:4443/some/my%20item/"), + URIs.childURI(URI.create("https://localhost:4443/some/"), "my item")); + } + +} diff --git a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/sparql/UpdatesTest.java b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/UpdatesTest.java similarity index 98% rename from cli/src/test/java/com/atomgraph/linkeddatahub/cli/sparql/UpdatesTest.java rename to rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/UpdatesTest.java index 79981c84b..20451080c 100644 --- a/cli/src/test/java/com/atomgraph/linkeddatahub/cli/sparql/UpdatesTest.java +++ b/rdf/src/test/java/com/atomgraph/linkeddatahub/rdf/UpdatesTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.atomgraph.linkeddatahub.cli.sparql; +package com.atomgraph.linkeddatahub.rdf; import java.net.URI; import org.apache.jena.query.Syntax; diff --git a/release.sh b/release.sh index addd5b7cf..c90bb2b8d 100755 --- a/release.sh +++ b/release.sh @@ -127,20 +127,24 @@ fi print_status "GPG check passed" -# Set cli/pom.xml to the given version and commit it. The CLI is not a module of the platform -# reactor, so maven-release-plugin does not rewrite it - it is kept in step here instead, once for -# the release version and once for the next development version. +# Set rdf/pom.xml and cli/pom.xml to the given version and commit them. Neither is a module of the +# platform reactor, so maven-release-plugin does not rewrite them - they are kept in step here +# instead, once for the release version and once for the next development version. The CLI resolves +# the library by ${project.version}, so the two must move together or the CLI build breaks. sync_cli_version() { local version="$1" + local project - (cd cli && mvn -B -q versions:set -DnewVersion="$version" -DgenerateBackupPoms=false) + for project in rdf cli; do + (cd "$project" && mvn -B -q versions:set -DnewVersion="$version" -DgenerateBackupPoms=false) + done - if git diff --quiet -- cli/pom.xml; then - print_status "cli/pom.xml already at $version" + if git diff --quiet -- rdf/pom.xml cli/pom.xml; then + print_status "rdf/pom.xml and cli/pom.xml already at $version" else - git add cli/pom.xml - git commit -m "Set the CLI version to $version" - print_status "cli/pom.xml set to $version" + git add rdf/pom.xml cli/pom.xml + git commit -m "Set the RDF library and CLI versions to $version" + print_status "rdf/pom.xml and cli/pom.xml set to $version" fi } From b6cf19c2d1c1001340006d3f01478097c6ac7a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Tue, 29 Sep 2026 22:34:05 +0200 Subject: [PATCH 2/3] Publish linkeddatahub-rdf to Maven Central alongside the platform The library had no distributionManagement and no publishing plugin, so a client outside this machine could not resolve it at all - REST-VKG's ldh-* operations would have had only a local install to depend on. rdf/pom.xml now declares the central-portal-snapshots repository and the central-publishing plugin, the licenses, developers and scm blocks Central rejects a release without, and a release profile attaching sources, javadoc and GPG signatures, mirroring what release activates for the platform. release.sh deploys it immediately after release:perform, which carries reactor modules only. It builds from the release tag via git archive rather than from the working tree, because sync_cli_version has moved rdf/pom.xml on to the next development version by that point. Inside the irreversible zone on purpose: a platform release whose pinned library is absent is a release whose clients cannot build. Building the release profile also caught a javadoc reference to SP#Describe, which the trimmed vocabulary does not carry - it holds Construct and Select. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 7 ++ rdf/pom.xml | 105 ++++++++++++++++++ .../atomgraph/linkeddatahub/rdf/Queries.java | 5 +- release.sh | 17 +++ 4 files changed, 131 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e41324b3c..c2e17250f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -186,6 +186,13 @@ drift. `cli/pom.xml` resolves the library by `${project.version}`, so the two mo `make cli` installs `rdf/` first; building `cli/` on its own needs `make rdf` to have run at least once since the last version bump. +`linkeddatahub-rdf` publishes to Maven Central on its own, since `release:perform` only carries +reactor modules: `release.sh` deploys it right after the platform, extracting `rdf/` from the release +tag with `git archive` because the working tree has already moved on to the next SNAPSHOT by then. +A snapshot can be published by hand with `cd rdf && mvn -Prelease clean deploy` — the `release` +profile attaches the sources and javadoc jars and signs them, which is what +`release` gets the platform. + `LDH_CERT_FILE`, `LDH_CERT_PASSWORD`, `LDH_BASE` and `LDH_PROXY` supply defaults for `-c`, `-p`, `-b` and `--proxy`. `-c/--cert` takes either format the agent's credential comes in — a PKCS12 keystore or a PEM file with the certificate and its PKCS#8 private key — told apart by content, not diff --git a/rdf/pom.xml b/rdf/pom.xml index 6c4986686..595a5c1f2 100644 --- a/rdf/pom.xml +++ b/rdf/pom.xml @@ -11,6 +11,42 @@ The RDF shapes LinkedDataHub documents are made of, and the vocabularies they are written in https://github.com/AtomGraph/LinkedDataHub + + + + namedgraph + martynas@atomgraph.com + + Developer + Founder + + AtomGraph + https://atomgraph.com + + + + + + Apache License 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + https://github.com/AtomGraph/LinkedDataHub + scm:git:git://github.com/AtomGraph/LinkedDataHub.git + scm:git:git@github.com:AtomGraph/LinkedDataHub.git + + + + + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 3.4.0 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 + + + sign-artifacts + verify + + sign + + + + + --pinentry-mode + loopback + + + + + + + + + + diff --git a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java index 5840f4e50..60da2763d 100644 --- a/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java +++ b/rdf/src/main/java/com/atomgraph/linkeddatahub/rdf/Queries.java @@ -36,9 +36,8 @@ public final class Queries private Queries() { } /** - * Builds a stored query. The query form is the caller's to state - {@link SP#Select}, - * {@link SP#Construct} and {@link SP#Describe} are all stored the same way, and only the - * type says which one this is. + * Builds a stored query. The query form is the caller's to state - {@link SP#Select} and + * {@link SP#Construct} are stored the same way, and only the type says which one this is. * * @param target target document URI * @param uri query URI, relative or absolute (optional, blank node when absent) diff --git a/release.sh b/release.sh index c90bb2b8d..b9f823a3b 100755 --- a/release.sh +++ b/release.sh @@ -232,6 +232,23 @@ print_status "Performing Maven release (deploying to Sonatype)..." mvn release:perform -DlocalCheckout=true PUBLISHED=true +# Publish linkeddatahub-rdf at the same version. It is not a module of the reactor, so +# release:perform does not carry it - but the CLI resolves it by ${project.version}, and Web-Algebra's +# ldh-* operations resolve it from their own repository, so a platform release without it is a release +# whose clients cannot build. +# +# Deployed from the tag rather than the working tree: by this point sync_cli_version has already moved +# rdf/pom.xml on to the next development version, while the tag holds the release one. git archive +# extracts the subtree alone, so nothing here depends on the working tree's state. +# The staging directory is removed inline rather than by a trap: the script's only EXIT trap is +# cleanup_on_failure, and registering a second one would replace it. +print_status "Deploying linkeddatahub-rdf $RELEASE_VERSION..." +RDF_STAGING=$(mktemp -d) +git archive "$RELEASE_TAG" rdf | tar -x -C "$RDF_STAGING" +(cd "$RDF_STAGING/rdf" && mvn -B -Prelease clean deploy) +rm -rf "$RDF_STAGING" +print_status "linkeddatahub-rdf $RELEASE_VERSION deployed" + # Switch to master and merge only the release commit print_status "Merging release commit to master branch..." git checkout master From a83d336e1eba720fef19e061b072af2f248905f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Tue, 29 Sep 2026 23:13:10 +0200 Subject: [PATCH 3/3] Declare the snapshot repository the CLI resolves its library from cli/ is its own build, so the platform pom's do not reach it, and it had never needed any of its own: everything it depended on is released to Maven Central. linkeddatahub-rdf between releases is not - Central serves no snapshots - so resolution succeeded only where the library happened to be in the local repository, which is to say on the machine that had just installed it. CI resolved nothing and failed on the dependency. Verified against a local repository that never had the install, which is the state CI starts from: the CLI builds and its 86 tests pass. Co-Authored-By: Claude Opus 5 (1M context) --- cli/pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cli/pom.xml b/cli/pom.xml index d59cc38a4..72fc6d4e4 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -17,6 +17,24 @@ 3.1.11 + + + + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + + + +