diff --git a/demo/northwind-traders/admin/model/ns.ttl b/demo/northwind-traders/admin/model/ns.ttl index 7812ceb..0dd03b6 100644 --- a/demo/northwind-traders/admin/model/ns.ttl +++ b/demo/northwind-traders/admin/model/ns.ttl @@ -125,6 +125,298 @@ schema:City spin:constructor :CityConstructor . WHERE {}""" ; rdfs:isDefinedBy : . +schema:Person spin:constructor :PersonConstructor . + +:PersonConstructor a ldh:Constructor ; + rdfs:label "Person constructor" ; + dct:title "Person constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:givenName [ a xsd:string ] ; + schema:familyName [ a xsd:string ] ; + schema:honorificPrefix [ a xsd:string ] ; + schema:jobTitle [ a xsd:string ] ; + schema:birthDate [ a xsd:date ] ; + schema:telephone [ a xsd:string ] ; + schema:description [ a xsd:string ] ; + schema:address [ a schema:PostalAddress ] ; + schema:sponsor [ a schema:Person ] ; + schema:areaServed [ a schema:City ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:Corporation spin:constructor :CorporationConstructor . + +:CorporationConstructor a ldh:Constructor ; + rdfs:label "Company constructor" ; + dct:title "Company constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:legalName [ a xsd:string ] ; + schema:identifier [ a xsd:string ] ; + schema:telephone [ a xsd:string ] ; + schema:contactPoint [ a schema:ContactPoint ] ; + schema:employee [ a schema:Person ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:ContactPoint spin:constructor :ContactPointConstructor . + +:ContactPointConstructor a ldh:Constructor ; + rdfs:label "Contact point constructor" ; + dct:title "Contact point constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + schema:telephone [ a xsd:string ] ; + schema:faxNumber [ a xsd:string ] ; + schema:url [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:PostalAddress spin:constructor :PostalAddressConstructor . + +:PostalAddressConstructor a ldh:Constructor ; + rdfs:label "Postal address constructor" ; + dct:title "Postal address constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + schema:streetAddress [ a xsd:string ] ; + schema:addressLocality [ a xsd:string ] ; + schema:addressRegion [ a xsd:string ] ; + schema:postalCode [ a xsd:string ] ; + schema:addressCountry [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:ProductGroup spin:constructor :ProductGroupConstructor . + +:ProductGroupConstructor a ldh:Constructor ; + rdfs:label "Category constructor" ; + dct:title "Category constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; + schema:description [ a xsd:string ] ; + schema:identifier [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:Place spin:constructor :PlaceConstructor . + +:PlaceConstructor a ldh:Constructor ; + rdfs:label "Region constructor" ; + dct:title "Region constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; + schema:identifier [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:Order spin:constructor :OrderConstructor . + +:OrderConstructor a ldh:Constructor ; + rdfs:label "Order constructor" ; + dct:title "Order constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:identifier [ a xsd:string ] ; + schema:orderDate [ a xsd:date ] ; + schema:customer [ a schema:Corporation ] ; + schema:broker [ a schema:Person ] ; + schema:orderedItem [ a schema:OrderItem ] ; + schema:orderDelivery [ a schema:ParcelDelivery ] ; + schema:acceptedOffer [ a schema:Offer ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:OrderItem spin:constructor :OrderItemConstructor . + +:OrderItemConstructor a ldh:Constructor ; + rdfs:label "Order item constructor" ; + dct:title "Order item constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + schema:orderedItem [ a schema:Product ] ; + schema:orderQuantity [ a xsd:integer ] ; + schema:price [ a xsd:float ] ; + schema:priceCurrency [ a xsd:string ] ; + schema:discount [ a xsd:float ] ; + schema:priceSpecification [ a schema:UnitPriceSpecification ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:ParcelDelivery spin:constructor :ParcelDeliveryConstructor . + +:ParcelDeliveryConstructor a ldh:Constructor ; + rdfs:label "Delivery constructor" ; + dct:title "Delivery constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + schema:partOfOrder [ a schema:Order ] ; + schema:provider [ a schema:Corporation ] ; + schema:deliveryAddress [ a schema:PostalAddress ] ; + schema:expectedArrivalUntil [ a xsd:date ] ; + schema:deliveryStatus [ a schema:DeliveryEvent ] ; + schema:price [ a xsd:float ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:DeliveryEvent spin:constructor :DeliveryEventConstructor . + +:DeliveryEventConstructor a ldh:Constructor ; + rdfs:label "Shipment constructor" ; + dct:title "Shipment constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:startDate [ a xsd:date ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:Offer spin:constructor :OfferConstructor . + +:OfferConstructor a ldh:Constructor ; + rdfs:label "Offer constructor" ; + dct:title "Offer constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:itemOffered [ a schema:Product ] ; + schema:price [ a xsd:float ] ; + schema:priceCurrency [ a xsd:string ] ; + schema:priceSpecification [ a schema:PriceSpecification ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:PriceSpecification spin:constructor :PriceSpecificationConstructor . + +:PriceSpecificationConstructor a ldh:Constructor ; + rdfs:label "Price specification constructor" ; + dct:title "Price specification constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:price [ a xsd:float ] ; + schema:priceCurrency [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:UnitPriceSpecification spin:constructor :UnitPriceSpecificationConstructor . + +:UnitPriceSpecificationConstructor a ldh:Constructor ; + rdfs:label "Unit price constructor" ; + dct:title "Unit price constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:price [ a xsd:float ] ; + schema:priceCurrency [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + +schema:DeliveryChargeSpecification spin:constructor :DeliveryChargeSpecificationConstructor . + +:DeliveryChargeSpecificationConstructor a ldh:Constructor ; + rdfs:label "Delivery charge constructor" ; + dct:title "Delivery charge constructor" ; + sp:text """ + PREFIX schema: + PREFIX dct: + PREFIX xsd: + + CONSTRUCT { + $this + dct:title [ a xsd:string ] ; + schema:price [ a xsd:float ] ; + schema:priceCurrency [ a xsd:string ] . + } + WHERE {}""" ; + rdfs:isDefinedBy : . + :MissingName a ldh:MissingPropertyValue ; rdfs:label "Missing schema:name" ; sp:arg1 schema:name ; diff --git a/docs/get-started/setup.ttl b/docs/get-started/setup.ttl index 9144937..a47fcbd 100644 --- a/docs/get-started/setup.ttl +++ b/docs/get-started/setup.ttl @@ -69,7 +69,7 @@ OWNER_COUNTRY_NAME=DK
LinkedDataHub/
 ├── config/     # dataspace and service configuration (dataspaces.trig, system.trig)
 ├── datasets/   # owner and secretary agent metadata
-├── fuseki/     # the backend triplestores' persisted RDF data
+├── fuseki/     # the triplestore's persisted RDF data, one TDB2 directory per dataset
 ├── ssl/
 │   ├── owner/      # your WebID certificate (keystore.p12)
 │   └── secretary/  # the service agent's certificate
diff --git a/docs/reference/configuration.ttl b/docs/reference/configuration.ttl
index e1fc6c0..1566c43 100644
--- a/docs/reference/configuration.ttl
+++ b/docs/reference/configuration.ttl
@@ -238,8 +238,14 @@ ABS_PATH=/linkeddatahub/
-

fuseki-admin and fuseki-end-user services

-

JAVA_OPTIONS carries the Java options of the Fuseki process, and is what sizes its heap. It defaults to -Xmx768m -Xms768m for fuseki-admin and -Xmx1536m -Xms1536m for fuseki-end-user.

+

fuseki service

+

A single Apache Jena Fuseki server holds a TDB2 dataset for every dataspace role — an end-user and an admin dataset per dataspace — declared in config/fuseki/config.ttl and persisted under the fuseki/ folder, one sub-folder per dataset. JAVA_OPTIONS carries the Java options of the Fuseki process, and is what sizes its heap.

+

Datasets are named after the dataspace origin with the deployment host dropped and the role appended — northwind-traders.demo.end-user, northwind-traders.demo.admin — so the names hold across development and production; the root dataspace's are plain end-user and admin. See the dataspace reference for the service declarations that bind an application to its datasets.

+
+
+

egress service

+

A forward proxy (Squid) that the outbound SPARQL SERVICE and LOAD requests of both Fuseki and the platform pass through. It permits public destinations and refuses loopback, private and link-local ones, resolving each where it connects so redirect hops and DNS answers are checked too. Federation with public endpoints keeps working, while a SERVICE clause cannot reach another dataset, the cache or the platform.

+

Fuseki is pointed at it through its JAVA_TOOL_OPTIONS; the platform's own in-process queries (imports and PATCH updates) are routed through it when EGRESS_PROXY is set to its host:port. Without a proxy and without ALLOW_INTERNAL_URLS, the platform disables SERVICE in those queries rather than leaving it open.

nginx service

diff --git a/docs/reference/dataspace.ttl b/docs/reference/dataspace.ttl index 4804bd6..7bac147 100644 --- a/docs/reference/dataspace.ttl +++ b/docs/reference/dataspace.ttl @@ -170,16 +170,17 @@

You can use either HTTP Basic authentication (username/password) or Bearer token authentication (auth token), but not both for the same service.

-

The default end-user service, which the Northwind Traders dataspace shares:

-
<urn:linkeddatahub:services/end-user>
+            

Each dataspace has its own end-user and admin service, pointing at its own datasets in the single fuseki server. A dataset is named after the dataspace origin with the deployment host dropped and the role appended, so the Northwind Traders end-user service reads northwind-traders.demo.end-user:

+
<urn:northwind-traders:services/end-user>
 {
-    <urn:linkeddatahub:services/end-user> a sd:Service ;
-        dct:title "LinkedDataHub service" ;
+    <urn:northwind-traders:services/end-user> a sd:Service ;
+        dct:title "Northwind Traders service" ;
         sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ;
-        sd:endpoint <http://fuseki-end-user:3030/ds/> ;
-        a:graphStore <http://fuseki-end-user:3030/ds/> ;
-        a:quadStore <http://fuseki-end-user:3030/ds/> .
+        sd:endpoint <http://fuseki:3030/northwind-traders.demo.end-user/> ;
+        a:graphStore <http://fuseki:3030/northwind-traders.demo.end-user/> ;
+        a:quadStore <http://fuseki:3030/northwind-traders.demo.end-user/> .
 }
+

The root dataspace's services keep the plain end-user and admin dataset names. Several dataspaces may still share one service where isolation is not required.

diff --git a/docs/reference/triplestores.ttl b/docs/reference/triplestores.ttl index a439873..345d64c 100644 --- a/docs/reference/triplestores.ttl +++ b/docs/reference/triplestores.ttl @@ -83,7 +83,7 @@ a:quadStore Quad store, for dataset-level CRUD No — omitting it falls back to graph-scoped GSP - <http://fuseki-host:3030/ds/>, which is Fuseki-specific + <http://fuseki:3030/end-user/>, which is Fuseki-specific diff --git a/docs/user-guide/manage-dataspaces.ttl b/docs/user-guide/manage-dataspaces.ttl index 2171bbb..bb70f90 100644 --- a/docs/user-guide/manage-dataspaces.ttl +++ b/docs/user-guide/manage-dataspaces.ttl @@ -30,7 +30,8 @@

To create a new dataspace:

  1. Add instances of lapp:EndUserApplication and lapp:AdminApplication to config/dataspaces.trig, following the default dataspace as a template
  2. -
  3. Add corresponding sd:Service instances to config/system.trig with the SPARQL endpoint connection details
  4. +
  5. Declare an end-user and an admin dataset for the dataspace in config/fuseki/config.ttl, named after its origin (deployment host dropped, role appended)
  6. +
  7. Add corresponding sd:Service instances to config/system.trig, each pointing at one of those datasets, and bind the applications to them

The relationship between end-user and admin applications is automatically inferred based on naming conventions — you do not need to specify lapp:endUserApplication or lapp:adminApplication properties manually. Applications whose origin (lapp:origin) uses the @@ -51,7 +52,7 @@

Manage services

-

The LinkedDataHub service, along with the default SPARQL services fuseki-end-user and fuseki-admin, is defined in docker-compose.yml; each runs as a Docker container.

+

The LinkedDataHub service, the single fuseki triplestore and the egress proxy are defined in docker-compose.yml; each runs as a Docker container. One Fuseki server holds an end-user and an admin dataset per dataspace, declared in config/fuseki/config.ttl — so giving a dataspace its own datasets means adding a dataset pair there and a matching pair of sd:Service instances in config/system.trig.

While LinkedDataHub uses Apache Jena Fuseki by default, you can configure it to use other SPARQL 1.1-compatible triplestores such as QLever, Tentris, or others.

See the triplestores reference for step-by-step configuration examples, including QLever setup and troubleshooting.

diff --git a/packages/README.md b/packages/README.md index 962080c..52ef452 100644 --- a/packages/README.md +++ b/packages/README.md @@ -88,19 +88,41 @@ Use `ldh:view` for forward relationships (resource has property) or `ldh:inverse ### 3. XSLT Stylesheet (named by `ac:stylesheet`) -XSLT templates using system modes to override default rendering: +XSLT template rules in the platform's **open modes**: ```xsl - + - - - + + + + + ``` -Available system modes include `ac:*` (Web-Client component modes named after the design system's components), `ldh:*` (LinkedDataHub components) and `xhtml:*` (XHTML elements). +The package stylesheet is composed into the platform's import tree right above `hooks.xsl`, the module +that declares the open modes and their generic fallbacks, and below everything else. Import precedence +beats template priority, so that position is the contract: a package rule outranks a fallback in an open +mode whatever the priorities, and loses to any rule in a sealed mode whatever its own priority. A package +cannot replace the page head, the content body, a typed rule or a global - those are sealed by +precedence, not by policy. + +An open mode is a leaf: it renders or contributes for one node and carries no control flow. The open +modes, with what a rule in each one owes: + +| Mode | What it renders | A package rule | +|---|---|---| +| `ldh:TreeNode` | one tree node | replaces the fallback, or decorates it with `xsl:next-match` | +| `ac:PropertyEditor` | one resource's property list, or one statement row | replaces or decorates; an empty rule hides | +| `ldh:ContentColumn` | the navigation slot beside the content body | fills it; nothing to inherit | +| `ldh:TreeChildrenLoad` | (client) the children fetch for one tree node | replaces | +| `ldh:RowHook` | (client) factories of deferred work for one rendered row | contributes; nothing to inherit | + +`hooks.xsl` in the platform sources carries the same table beside the declarations. The value-leaf modes +(`ac:FormControl`, `ac:PropertyListValue`, the unnamed mode) and the component modes (`ldh:Modal`, +`ac:FieldShell`...) are sealed in this version. ## Installing Packages @@ -140,7 +162,8 @@ From the next request onwards, the server resolves it: an `owl:imports` of the namespace ontology. Its classes, constructors, constraints and views become available on the `ns` endpoint and in the UI. 3. **Composes the package stylesheet** (`ac:stylesheet`) into the application stylesheet by - appending an `xsl:import` after the existing ones, so package templates override the system's. + inserting an `xsl:import` right after the platform's `hooks.xsl` import, so package templates + override the open modes' fallbacks and nothing else (see the stylesheet section above). Packages are applied in the order of their URIs. One that declares only an ontology, or only a stylesheet, contributes only that; one whose description cannot be resolved is skipped. If the @@ -179,6 +202,6 @@ List of available packages can be found in the [LinkedDataHub-Apps](https://gith - Packages are **declarative only** (RDF + XSLT, no Java code) - Package ontologies use `owl:imports` (handled automatically by Jena) -- Package stylesheets are composed into the application stylesheet with `xsl:import`, in memory, per dataspace +- Package stylesheets are composed into the application stylesheet with `xsl:import` at the `hooks.xsl` marker, in memory, per dataspace - Property views (`ldh:view`/`ldh:inverseView`) are separate from XSLT overrides - Both mechanisms work independently and complement each other diff --git a/packages/editor/taxonomy/skos.xsl b/packages/editor/taxonomy/skos.xsl index 9182081..7a1b958 100644 --- a/packages/editor/taxonomy/skos.xsl +++ b/packages/editor/taxonomy/skos.xsl @@ -148,16 +148,17 @@ exclude-result-prefixes="#all"> The tree roots at the scheme, so on a concept page it must open the path down to that concept or the reader is left at the top of a taxonomy with no idea where they are. - Triggered from ldh:RenderRow, which the platform already applies to every direct child of - .content-body after the pane is in the DOM - on a direct load AND on a client-side navigation - alike. That is the whole reason no new platform hook was needed, and why there is one + Triggered through ldh:RowHook, the open mode the platform's ldh:RenderRow walk applies to every + element under .content-body after the pane is in the DOM - on a direct load AND on a client-side + navigation alike. That is the whole reason no new platform hook was needed, and why there is one implementation rather than a synchronous server walk beside an asynchronous client one: this - mode is the only place both paths meet after the markup exists. + mode is the only place both paths meet after the markup exists. The walk itself is sealed; this + rule only returns work for the aside and never descends, so nothing under it is skipped. The mode's contract is a FACTORY, not work: it is evaluated inside a non-updating variable binding and the factories are invoked later inside ixsl:promise, which is also what gives ixsl:http-request the active promise it requires. --> - +