Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
49f22f6
[maven-release-plugin] prepare for next development iteration
namedgraph Sep 29, 2026
a56deca
Set the CLI version to 6.0.1-SNAPSHOT
namedgraph Sep 29, 2026
b94630f
Post-release version bump
namedgraph Sep 29, 2026
9449470
Proxied XHTML content resolves path-absolute references against the d…
namedgraph Sep 29, 2026
a4b5a55
The documentation links point at docs.linkeddatahub.com
namedgraph Sep 29, 2026
b87fc9b
A 429 retry re-sends the request its step made, not the first request…
namedgraph Sep 29, 2026
0ffe1a4
ldh:css-token() asks getComputedStyle for the custom property by name…
namedgraph Sep 29, 2026
9f9e1ea
The chart block is asserted to draw its own results after its results…
namedgraph Sep 29, 2026
7a774fc
A load suite, tests/load, bursts the running stack with renders and a…
namedgraph Sep 29, 2026
9372879
The 429 retry is asserted as an axis, not a chart spec: axes/rate-lim…
namedgraph Sep 29, 2026
5cc4b0c
A request to this instance's own URL is bounded on its own, and the c…
namedgraph Sep 29, 2026
ef9fb0b
Package ontologies no longer take their dataspace down, and packages.…
namedgraph Sep 29, 2026
71412f2
The package tests import from a registry in the test stack instead of…
namedgraph Sep 29, 2026
fbafc5a
The taxonomy editor package is called the Taxonomy Editor, not the SK…
namedgraph Sep 29, 2026
a76d553
Every write to a document is held to the constraints of what it write…
namedgraph Sep 29, 2026
a52b329
Extract the vocabularies and document shapes into linkeddatahub-rdf (…
namedgraph Sep 29, 2026
a936a27
The next release is 6.0.1, and the changelog catches up with what has…
namedgraph Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/load-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Load-tests

on: push

jobs:
load-tests:
name: Build Docker image and run the load test suite against a small stack
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Add bin/ and its subdirectories to PATH
run: |
find "$GITHUB_WORKSPACE/bin" -type d >> "$GITHUB_PATH"
- name: Generating server certificate
run: |
server-cert-gen.sh .env nginx ssl
working-directory: tests/http
- name: Writing secrets to files
run: |
mkdir -p ./secrets
printf "%s" "${{ secrets.HTTP_TEST_OWNER_CERT_PASSWORD }}" > ./secrets/owner_cert_password.txt
printf "%s" "${{ secrets.HTTP_TEST_SECRETARY_CERT_PASSWORD }}" > ./secrets/secretary_cert_password.txt
printf "%s" "${{ secrets.HTTP_TEST_SECRETARY_CERT_PASSWORD }}" > ./secrets/client_truststore_password.txt
shell: bash
- name: Build Docker image & Run Docker containers
# the HTTP suite's fixtures, with the connector and client pool shrunk by the load override
run: docker compose -f docker-compose.yml -f ./tests/http/docker-compose.http-tests.yml -f ./tests/load/docker-compose.load-tests.yml --env-file ./tests/http/.env up --build -d
- name: Wait for the server to start...
run: while ! (status=$(curl -k -s -w "%{http_code}\n" https://localhost:4443 -o /dev/null) && echo "$status" && echo "$status" | grep "403") ; do sleep 1 ; done # wait for the webapp to start (returns 403 by default)
- name: Run load test scripts
run: ./run.sh
shell: bash
working-directory: tests/load
- name: Generate test summary
if: always()
run: python3 scripts/generate_test_summary.py tests/load/out tests/load/out/report.md
- name: Write job summary
if: always() && hashFiles('tests/load/out/report.md') != ''
run: cat tests/load/out/report.md >> "$GITHUB_STEP_SUMMARY"
- name: Collect container logs on failure
if: failure()
run: |
mkdir -p tests/load/out/containers
timeout 300 docker compose --env-file ./tests/http/.env logs --no-color > tests/load/out/containers/compose.log 2>&1 || true
timeout 300 docker compose --env-file ./tests/http/.env exec -T linkeddatahub sh -c 'for f in /usr/local/tomcat/logs/*; do echo "=== $f ==="; cat "$f"; done' > tests/load/out/containers/tomcat.log 2>&1 || true
- name: Upload test results and container logs
if: always() && hashFiles('tests/load/out/**') != ''
uses: actions/upload-artifact@v4
with:
name: load-test-results
path: tests/load/out/
if-no-files-found: ignore
- name: Stop Docker containers and remove volumes
run: docker compose --env-file ./tests/http/.env down -v
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
/tests/http/datasets
/tests/http/uploads
/tests/http/out
/tests/load/out
/tests/load/settings/*
!/tests/load/settings/.gitkeep
/fuseki
.claude/scheduled_tasks.lock
/cli/target
/tests/ui/node_modules
/tests/ui/out
/tests/ui/test-results
/rdf/target/
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## [6.0.1] - 2026-09-30
### Added
- `linkeddatahub-rdf`, a separately published library of the vocabularies and the document shapes the HTTP API accepts, so the CLI and other clients build against one description of the request bodies

### Changed
- Documentation links point at `docs.linkeddatahub.com` rather than the stale static mirror under `atomgraph.github.io`

### Fixed
- A burst of server-side renders could deadlock the platform against itself, each render holding a request thread while it waited for a label callback that needed another; a request to this instance's own URL is now bounded by `CLIENT_SELF_REQUEST_TIMEOUT` (5 s) and the image's client pool is sized to the connector (`MAX_CONN_PER_ROUTE=200`, `MAX_TOTAL_CONN=400`)
- Every write is held to the constraints of the document as it will be written: a PUT that did not type the document was validated while the document was untyped, a POST validated only its body, and the document form's multipart PUT wrote the form's triples as the graph with no type, container, timestamps or `If-Match`
- A package ontology whose descriptor named its document rather than the ontology took its dataspace down, and bundled copies shadowed `packages.linkeddatahub.com`, so every page of the packages dataspace failed with an empty label
- Proxied XHTML content left path-absolute `@href`/`@src` references unresolved, so authored media (`/uploads/{sha1}`) resolved against the proxying dataspace's origin and 404'd
- A block retrying a `429` re-sent the first request of its chain instead of the one its step made, so a rate-limited chart drew the RDF/XML of its query's document as the results
- Chart colours came out empty in Chromium, which enumerates no custom properties: `ldh:css-token()` asks `getComputedStyle` for the property by name instead of reading the enumerated style map
- `ldh` uploads `.xsl` and `.xslt` as `text/xsl`; stored as `application/octet-stream`, a stylesheet answered the platform's request with `406`

## [6.0.0] - 2026-09-29
LinkedDataHub has a new interface. The app shell, content blocks, action bar, breadcrumbs, mode lists, type badges, property lists, tables, pager, modals and forms are drawn against a design system vendored into the platform — its tokens, components and typefaces ship with LDH, and `ldh.css` is the single app layer loaded over them, the one file a dataspace stylesheet has to reckon with. IXSL templates drive the dropdowns and modals, the `msi` font draws the icons and the RDFa editor edits `rdf:XMLLiteral`, so jQuery, `bootstrap.js`, WYMEditor and the sprite sheet are gone.

Expand Down
40 changes: 34 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -155,15 +156,42 @@ 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 `<modules>`), 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 `<modules>`), 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.

`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
`<releaseProfiles>release</releaseProfiles>` 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
Expand Down
16 changes: 14 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,12 @@ ENV OIDC_REFRESH_TOKENS=/var/linkeddatahub/oidc/refresh_tokens.properties

ENV MAX_CONTENT_LENGTH=2097152

ENV MAX_CONN_PER_ROUTE=20
# sized to the connector's 200 threads: a request thread that calls back into this instance holds one pooled
# connection while it waits for another thread to answer, so a pool smaller than the connector queues
# those calls behind each other, one bounded wait at a time, and a burst of renders drains one slot at a time
ENV MAX_CONN_PER_ROUTE=200

ENV MAX_TOTAL_CONN=40
ENV MAX_TOTAL_CONN=400

ENV MAX_REQUEST_RETRIES=3

Expand All @@ -193,6 +196,11 @@ ENV CLIENT_SOCKET_TIMEOUT=120000

ENV CLIENT_CONNECT_TIMEOUT=10000

# for requests to this instance's own URLs, answered by its own request threads: a label lookup takes milliseconds
# through the proxy, and this is also the longest a burst of renders can hold every thread waiting on each other
# (a render makes up to three in sequence)
ENV CLIENT_SELF_REQUEST_TIMEOUT=5000

ENV CLIENT_CONNECTION_TIME_TO_LIVE=300000

ENV CLIENT_VALIDATE_AFTER_INACTIVITY=10000
Expand Down Expand Up @@ -227,6 +235,10 @@ COPY platform/context.xsl /var/linkeddatahub/xsl/context.xsl

COPY platform/web.xsl /var/linkeddatahub/xsl/web.xsl

# add XSLT stylesheet that makes changes to server.xml, after letsencrypt-tomcat.xsl has generated it

COPY platform/server.xsl /var/linkeddatahub/xsl/server.xsl

# copy entrypoint

COPY platform/entrypoint.sh entrypoint.sh
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ What makes LinkedDataHub unique is its completely _data-driven architecture_: ap

XHTML documents can be edited in-place using a built-in RDFa-aware rich text editor — annotations link selected text directly to Knowledge Graph terms, embedding machine-readable RDF statements in the markup without leaving the page.

**Follow the [Get started](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/get-started/) guide to LinkedDataHub.** The setup and basic configuration sections are provided below and should get you running.
**Follow the [Get started](https://docs.linkeddatahub.com/get-started/) guide to LinkedDataHub.** The setup and basic configuration sections are provided below and should get you running.

**LinkedDataHub is also available as a free AWS Marketplace product!** <a href="https://aws.amazon.com/marketplace/pp/prodview-vqbeztc3f2nni" target="_blank"><img src="https://github.com/AtomGraph/LinkedDataHub/raw/master/AWS%20Marketplace.svg" width="160" alt="AWS Marketplace"/></a>
It takes a few clicks and filling out a form to install the product into your own AWS account. No manual setup or configuration necessary!
Expand Down Expand Up @@ -247,7 +247,7 @@ _:warning: Do not use blank nodes to identify applications or services. We recom
<dd>Port number of the mail server</dd>
</dl>

The options are described in more detail in the [configuration documentation](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/reference/configuration/).
The options are described in more detail in the [configuration documentation](https://docs.linkeddatahub.com/reference/configuration/).

## Reset

Expand All @@ -260,13 +260,13 @@ The options are described in more detail in the [configuration documentation](ht
_:warning: This will **remove the persisted data and files** as well as Docker volumes._
</details>

## [Documentation](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/)
## [Documentation](https://docs.linkeddatahub.com/)

* [Get started](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/get-started/)
* [Reference](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/reference/)
* [User guide](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/user-guide/)
* [Get started](https://docs.linkeddatahub.com/get-started/)
* [Reference](https://docs.linkeddatahub.com/reference/)
* [User guide](https://docs.linkeddatahub.com/user-guide/)

## [Command line interface](https://atomgraph.github.io/LinkedDataHub/linkeddatahub/docs/reference/command-line-interface/)
## [Command line interface](https://docs.linkeddatahub.com/reference/command-line-interface/)

`ldh` wraps the HTTP API into a single executable with convenient parameters. It can be used for testing, automation, scheduled execution and such. It is usually much quicker to perform actions using the CLI rather than the user interface, as well as easier to reproduce.

Expand Down
6 changes: 6 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
27 changes: 26 additions & 1 deletion cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.atomgraph</groupId>
<artifactId>linkeddatahub-cli</artifactId>
<version>6.0.0</version>
<version>6.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>LinkedDataHub CLI</name>
Expand All @@ -17,7 +17,32 @@
<jersey.version>3.1.11</jersey.version>
</properties>

<!-- where linkeddatahub-rdf comes from between releases. This project has never needed a
repository before: everything it depends on is released to Maven Central, which does not serve
snapshots, and the platform pom's own declaration does not reach here because the two are
separate builds. Without this a clean checkout resolves the library only if it happens to be in
the local repository, which is how CI first failed. -->
<repositories>
<repository>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<dependencies>
<dependency>
<!-- the vocabularies and the document shapes, shared with the platform's other API
clients; this project adds the command line over them -->
<groupId>com.atomgraph</groupId>
<artifactId>linkeddatahub-rdf</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
Expand Down
15 changes: 0 additions & 15 deletions cli/src/main/java/com/atomgraph/linkeddatahub/cli/BaseCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <code>--uri</code> 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 <code>--uri</code> 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' <code>turtle --base</code> piping).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 (<code>sp:Construct</code> or <code>sp:Select</code>)
* @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));
}

}
Loading
Loading