Architecture
- Design goal: Spring is only a bootstrapper
- Package map
- Metadata store (pluggable SPARQL backend)
- Request flow
Design goal: Spring is only a bootstrapper
The server is deliberately structured so that Spring Boot is only a bootstrapper and can be removed later in favour of a bare Eclipse Jetty deployment:
- Every piece of protocol logic is a plain Jakarta
HttpServlet/Filteror a framework-free service object. None of it imports Spring. - The entire object graph is wired by one annotation-free factory,
LwsComponents. - The only Spring-aware classes are
LwsServer(the@SpringBootApplicationmain) andLwsServletConfig(one@Configurationthat registers the servlets/filters). JettyLauncheris a no-Springmainthat registers the very same servlets/filters on a hand-built JettyServer— the line-for-line analogue ofLwsServletConfig. Migrating off Spring is: delete those two Spring classes and shipJettyLauncher.
The bare-Jetty launcher is also the only path that can terminate TLS itself (the Spring entry point is intended for the reverse-proxy model).
Package map
com.ebremer.lws.server
├─ LwsServer @SpringBootApplication main (Spring – bootstrap only)
├─ LwsServletConfig @Configuration: registers servlets/filters (Spring – bootstrap only)
├─ JettyLauncher bare Eclipse Jetty main (no Spring) – the migration target
├─ LwsComponents framework-free wiring of the whole object graph
├─ LwsConfiguration immutable config POJO (no framework deps)
├─ vocab/ LWS, Activity Streams 2.0, LDP vocabularies (Apache Jena)
├─ rdf/ RdfStore abstraction; Tdb2RdfStore + RemoteSparqlRdfStore; formats/IO
├─ storage/ BinaryStore abstraction + FileSystemBinaryStore (non-RDF bytes)
├─ core/ resource model, ResourceService (the protocol engine), registry,
│ storage description, access policy, IRIs/etags
├─ auth/ credential validators, DPoP, Shiro realm/filter, WAC, SSRF guard
├─ notifications/ subscriptions, webhook dispatch, RFC 9421 signatures, Ed25519 keys
├─ tls/ ACME (Let's Encrypt) certificate provisioning for the bare-Jetty launcher
├─ http/ the Jakarta servlets (resource, storage-description, subscriptions, jwks)
└─ ui/ Apache Wicket storage browser
Metadata store (pluggable SPARQL backend)
All RDF goes through RdfStore, which hands the service layer a Jena RDFConnection. Jena
implements that connection identically over a local TDB2 dataset and over any remote SPARQL 1.1
service (Query + Update + Graph Store Protocol), so the backend is a one-line wiring choice
(lws.sparql.mode):
- TDB2 (default) — embedded, transactional, zero-ops.
- Remote SPARQL — point at Fuseki, GraphDB, Blazegraph, Neptune, … (see Configuration).
What is stored where
- RDF resource content — one named graph per resource, named by the resource IRI.
- Non-RDF resource content — opaque bytes in a
BinaryStore(filesystem by default), with the resource’s metadata (media type, size, SHA-256 digest, binary key) in the RDF store. - Administrative metadata — type, containment, timestamps, etags, owner, and pointers live in a
dedicated
urn:x-lws:admingraph, separate from content graphs. - Other internal graphs — user-managed linkset metadata, subscriptions, access grants, and per-resource WAC ACLs each live in their own named graph.
This separation keeps content graphs pure RDF (exactly what the client wrote) while the server’s bookkeeping never mixes in.
Request flow
AuthenticationFilterestablishes the principal from theAuthorization(andDPoP) headers — see Authentication — and binds it to a Shiro subject and a thread-localRequestContext(carrying the requestOriginand any declared purposes). It is pass-through: it identifies the caller but does not itself authorize.- The matching servlet (
LwsResourceServletfor the resource space, plus dedicated servlets for the storage description, subscriptions, JWKS, search and access services) handles the method. ResourceService— the protocol engine — performs the operation transactionally against theRdfStoreandBinaryStore, consulting the configuredAuthorizer(owner-based or WAC, with access grants layered on top) for every access decision, and emits resource events.- Event listeners (the notification emitter, the linkset metadata janitor, the search index, and the WAC ACL janitor) react to those events.
Because authorization is decided in the service layer, the HTTP servlets, the Wicket UI, and the search services all enforce exactly the same rules.