Skip to content

Commit 38a1ac9

Browse files
committed
System config dataset uses named graphs
1 parent ec4e7e6 commit 38a1ac9

File tree

5 files changed

+200
-73
lines changed

5 files changed

+200
-73
lines changed

config/system.trig

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,47 @@
1515

1616
# root admin
1717

18-
<urn:linkeddatahub:apps/admin> a lapp:Application, lapp:AdminApplication ;
19-
dct:title "LinkedDataHub admin" ;
20-
# ldt:base <https://admin.localhost:4443/> ;
21-
lapp:origin <https://admin.localhost:4443> ;
22-
ldt:ontology <https://w3id.org/atomgraph/linkeddatahub/admin#> ;
23-
ldt:service <urn:linkeddatahub:services/admin> ;
24-
ac:stylesheet <static/xsl/admin/layout.xsl> ;
25-
lapp:endUserApplication <urn:linkeddatahub:apps/end-user> ;
26-
lapp:frontendProxy <http://varnish-frontend:6060/> .
18+
<urn:linkeddatahub:apps/admin>
19+
{
20+
<urn:linkeddatahub:apps/admin> a lapp:Application, lapp:AdminApplication ;
21+
dct:title "LinkedDataHub admin" ;
22+
# ldt:base <https://admin.localhost:4443/> ;
23+
lapp:origin <https://admin.localhost:4443> ;
24+
ldt:ontology <https://w3id.org/atomgraph/linkeddatahub/admin#> ;
25+
ldt:service <urn:linkeddatahub:services/admin> ;
26+
ac:stylesheet <static/xsl/admin/layout.xsl> ;
27+
lapp:endUserApplication <urn:linkeddatahub:apps/end-user> ;
28+
lapp:frontendProxy <http://varnish-frontend:6060/> .
2729

28-
<urn:linkeddatahub:services/admin> a sd:Service ;
29-
dct:title "LinkedDataHub admin service" ;
30-
sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ;
31-
sd:endpoint <http://fuseki-admin:3030/ds/> ;
32-
a:graphStore <http://fuseki-admin:3030/ds/> ;
33-
a:quadStore <http://fuseki-admin:3030/ds/> ;
34-
lapp:backendProxy <http://varnish-admin/> .
30+
<urn:linkeddatahub:services/admin> a sd:Service ;
31+
dct:title "LinkedDataHub admin service" ;
32+
sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ;
33+
sd:endpoint <http://fuseki-admin:3030/ds/> ;
34+
a:graphStore <http://fuseki-admin:3030/ds/> ;
35+
a:quadStore <http://fuseki-admin:3030/ds/> ;
36+
lapp:backendProxy <http://varnish-admin/> .
37+
}
3538

3639
# root end-user
3740

38-
<urn:linkeddatahub:apps/end-user> a lapp:Application, lapp:EndUserApplication ;
39-
dct:title "LinkedDataHub" ;
40-
# ldt:base <https://localhost:4443/> ;
41-
lapp:origin <https://localhost:4443> ;
42-
ldt:ontology <https://localhost:4443/ns#> ;
43-
ldt:service <urn:linkeddatahub:services/end-user> ;
44-
ac:stylesheet <static/xsl/layout.xsl> ;
45-
lapp:adminApplication <urn:linkeddatahub:apps/admin> ;
46-
lapp:frontendProxy <http://varnish-frontend:6060/> ;
47-
lapp:public true .
41+
<urn:linkeddatahub:apps/end-user>
42+
{
43+
<urn:linkeddatahub:apps/end-user> a lapp:Application, lapp:EndUserApplication ;
44+
dct:title "LinkedDataHub" ;
45+
# ldt:base <https://localhost:4443/> ;
46+
lapp:origin <https://localhost:4443> ;
47+
ldt:ontology <https://localhost:4443/ns#> ;
48+
ldt:service <urn:linkeddatahub:services/end-user> ;
49+
ac:stylesheet <static/xsl/layout.xsl> ;
50+
lapp:adminApplication <urn:linkeddatahub:apps/admin> ;
51+
lapp:frontendProxy <http://varnish-frontend:6060/> ;
52+
lapp:public true .
4853

49-
<urn:linkeddatahub:services/end-user> a sd:Service ;
50-
dct:title "LinkedDataHub service" ;
51-
sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ;
52-
sd:endpoint <http://fuseki-end-user:3030/ds/> ;
53-
a:graphStore <http://fuseki-end-user:3030/ds/> ;
54-
a:quadStore <http://fuseki-end-user:3030/ds/> ;
55-
lapp:backendProxy <http://varnish-end-user/> .
54+
<urn:linkeddatahub:services/end-user> a sd:Service ;
55+
dct:title "LinkedDataHub service" ;
56+
sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ;
57+
sd:endpoint <http://fuseki-end-user:3030/ds/> ;
58+
a:graphStore <http://fuseki-end-user:3030/ds/> ;
59+
a:quadStore <http://fuseki-end-user:3030/ds/> ;
60+
lapp:backendProxy <http://varnish-end-user/> .
61+
}

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ services:
9393
- ./datasets/secretary:/var/linkeddatahub/datasets/secretary
9494
- ./uploads:/var/www/linkeddatahub/uploads
9595
- ./config/dev.log4j.properties:/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/log4j.properties:ro
96-
- ./config/system.trig:/var/linkeddatahub/datasets/system.trig:ro
96+
- ./config/system.trig:/var/linkeddatahub/datasets/system.trig
9797
fuseki-admin:
9898
image: atomgraph/fuseki:4.7.0
9999
user: root # otherwise fuseki user does not have permissions to the mounted folder which is owner by root

platform/select-root-services.rq

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,37 @@ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
77

88
SELECT ?endUserApp ?endUserOrigin ?endUserQuadStore ?endUserEndpoint ?endUserAuthUser ?endUserAuthPwd ?endUserMaker ?adminApp ?adminOrigin ?adminQuadStore ?adminEndpoint ?adminAuthUser ?adminAuthPwd ?adminMaker
99
{
10-
?endUserApp lapp:origin ?endUserOrigin ;
11-
ldt:service ?endUserService ;
12-
lapp:adminApplication ?adminApp .
13-
?adminApp ldt:service ?adminService ;
14-
lapp:origin ?adminOrigin .
15-
?endUserService a:quadStore ?endUserQuadStore ;
16-
sd:endpoint ?endUserEndpoint .
17-
?adminService a:quadStore ?adminQuadStore ;
18-
sd:endpoint ?adminEndpoint .
19-
OPTIONAL
20-
{
21-
?endUserService a:authUser ?endUserAuthUser ;
22-
a:authPwd ?endUserAuthPwd .
23-
}
24-
OPTIONAL
10+
GRAPH ?endUserAppGraph
2511
{
26-
?adminService a:authUser ?adminAuthUser ;
27-
a:authPwd ?adminAuthPwd .
12+
?endUserApp lapp:origin ?endUserOrigin ;
13+
ldt:service ?endUserService ;
14+
lapp:adminApplication ?adminApp .
15+
?endUserService a:quadStore ?endUserQuadStore ;
16+
sd:endpoint ?endUserEndpoint .
17+
OPTIONAL
18+
{
19+
?endUserService a:authUser ?endUserAuthUser ;
20+
a:authPwd ?endUserAuthPwd .
21+
}
22+
OPTIONAL
23+
{
24+
?endUserService foaf:maker ?endUserMaker
25+
}
2826
}
29-
OPTIONAL
27+
GRAPH ?adminAppGraph
3028
{
31-
?endUserService foaf:maker ?endUserMaker
32-
}
33-
OPTIONAL
34-
{
35-
?adminService foaf:maker ?adminMaker
29+
?adminApp ldt:service ?adminService ;
30+
lapp:origin ?adminOrigin .
31+
?adminService a:quadStore ?adminQuadStore ;
32+
sd:endpoint ?adminEndpoint .
33+
OPTIONAL
34+
{
35+
?adminService a:authUser ?adminAuthUser ;
36+
a:authPwd ?adminAuthPwd .
37+
}
38+
OPTIONAL
39+
{
40+
?adminService foaf:maker ?adminMaker
41+
}
3642
}
3743
}

src/main/java/com/atomgraph/linkeddatahub/Application.java

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@
176176
import java.io.FileOutputStream;
177177
import java.io.UnsupportedEncodingException;
178178
import java.nio.charset.StandardCharsets;
179+
import java.nio.file.Path;
180+
import java.nio.file.Paths;
179181
import java.security.MessageDigest;
180182
import java.util.Arrays;
181183
import java.util.List;
@@ -294,9 +296,9 @@ public class Application extends ResourceConfig
294296
private final boolean enableWebIDSignUp;
295297
private final String oidcRefreshTokensPropertiesPath;
296298
private final Properties oidcRefreshTokens;
299+
private final URI contextDatasetURI;
300+
private final Dataset contextDataset;
297301

298-
private Dataset contextDataset;
299-
300302
/**
301303
* Constructs system application and configures it using sevlet config.
302304
*
@@ -313,6 +315,7 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti
313315
servletConfig.getServletContext().getInitParameter(A.cacheModelLoads.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(A.cacheModelLoads.getURI())) : true,
314316
servletConfig.getServletContext().getInitParameter(A.preemptiveAuth.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(A.preemptiveAuth.getURI())) : false,
315317
new PrefixMapper(servletConfig.getServletContext().getInitParameter(AC.prefixMapping.getURI()) != null ? servletConfig.getServletContext().getInitParameter(AC.prefixMapping.getURI()) : null),
318+
servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI()) : null,
316319
com.atomgraph.client.Application.getSource(servletConfig.getServletContext(), servletConfig.getServletContext().getInitParameter(AC.stylesheet.getURI()) != null ? servletConfig.getServletContext().getInitParameter(AC.stylesheet.getURI()) : null),
317320
servletConfig.getServletContext().getInitParameter(AC.cacheStylesheet.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(AC.cacheStylesheet.getURI())) : false,
318321
servletConfig.getServletContext().getInitParameter(AC.resolvingUncached.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(AC.resolvingUncached.getURI())) : true,
@@ -355,14 +358,6 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti
355358
servletConfig.getServletContext().getInitParameter(ORCID.clientID.getURI()) != null ? servletConfig.getServletContext().getInitParameter(ORCID.clientID.getURI()) : null,
356359
servletConfig.getServletContext().getInitParameter(ORCID.clientSecret.getURI()) != null ? servletConfig.getServletContext().getInitParameter(ORCID.clientSecret.getURI()) : null
357360
);
358-
359-
URI contextDatasetURI = servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI()) != null ? new URI(servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI())) : null;
360-
if (contextDatasetURI == null)
361-
{
362-
if (log.isErrorEnabled()) log.error("Context dataset URI '{}' not configured", LDHC.contextDataset.getURI());
363-
throw new ConfigurationException(LDHC.contextDataset);
364-
}
365-
this.contextDataset = getDataset(servletConfig.getServletContext(), contextDatasetURI);
366361
}
367362

368363
/**
@@ -374,6 +369,7 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti
374369
* @param cacheModelLoads true if model loads should be cached
375370
* @param preemptiveAuth true if HTTP Basic auth credentials should be sent preemptively
376371
* @param locationMapper Jena's <code>LocationMapper</code> instance
372+
* @param contextDatasetURIString location of the context dataset
377373
* @param stylesheet stylesheet URI
378374
* @param cacheStylesheet true if stylesheet should be cached
379375
* @param resolvingUncached true if XLST processor should dereference URLs that are not cached
@@ -418,7 +414,8 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti
418414
*/
419415
public Application(final ServletConfig servletConfig, final MediaTypes mediaTypes,
420416
final Integer maxGetRequestSize, final boolean cacheModelLoads, final boolean preemptiveAuth,
421-
final LocationMapper locationMapper, final Source stylesheet, final boolean cacheStylesheet, final boolean resolvingUncached,
417+
final LocationMapper locationMapper, final String contextDatasetURIString,
418+
final Source stylesheet, final boolean cacheStylesheet, final boolean resolvingUncached,
422419
final String clientKeyStoreURIString, final String clientKeyStorePassword,
423420
final String secretaryCertAlias,
424421
final String clientTrustStoreURIString, final String clientTrustStorePassword,
@@ -433,6 +430,13 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType
433430
final String googleClientID, final String googleClientSecret,
434431
final String orcidClientID, final String orcidClientSecret)
435432
{
433+
if (contextDatasetURIString == null)
434+
{
435+
if (log.isErrorEnabled()) log.error("Context dataset URI '{}' not configured", LDHC.contextDataset.getURI());
436+
throw new ConfigurationException(LDHC.contextDataset);
437+
}
438+
this.contextDatasetURI = URI.create(contextDatasetURIString);
439+
436440
if (clientKeyStoreURIString == null)
437441
{
438442
if (log.isErrorEnabled()) log.error("Client key store ({}) not configured", LDHC.clientKeyStore.getURI());
@@ -664,6 +668,8 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType
664668

665669
try
666670
{
671+
this.contextDataset = getDataset(servletConfig.getServletContext(), contextDatasetURI);
672+
667673
keyStore = KeyStore.getInstance("PKCS12");
668674
try (FileInputStream keyStoreInputStream = new FileInputStream(new java.io.File(new URI(clientKeyStoreURIString))))
669675
{
@@ -1974,27 +1980,77 @@ public URI getUploadRoot()
19741980

19751981
/**
19761982
* Returns RDF dataset with LinkedDataHub application descriptions.
1977-
*
1983+
*
19781984
* @return RDF dataset
19791985
*/
19801986
protected Dataset getContextDataset()
19811987
{
19821988
return contextDataset;
19831989
}
19841990

1991+
/**
1992+
* Returns the URI of the context dataset file.
1993+
*
1994+
* @return context dataset URI
1995+
*/
1996+
protected URI getContextDatasetURI()
1997+
{
1998+
return contextDatasetURI;
1999+
}
2000+
19852001
/**
19862002
* Returns RDF model with LinkedDataHub application descriptions.
1987-
*
1988-
* @return RDF model
2003+
* This method returns a union of all named graphs from the context dataset.
2004+
*
2005+
* @return RDF model (read-only union of all named graphs)
19892006
*/
19902007
public Model getContextModel()
19912008
{
1992-
return ModelFactory.createModelForGraph(new GraphReadOnly(getContextDataset().getDefaultModel().getGraph()));
2009+
return ModelFactory.createModelForGraph(new GraphReadOnly(getContextDataset().getUnionModel().getGraph()));
2010+
}
2011+
2012+
/**
2013+
* Updates a dataspace by replacing its named graph with a new Model.
2014+
* This is a template method that can be overridden by subclasses to provide alternative implementations
2015+
* (e.g., HTTP-based updates using GraphStoreClient to a remote triplestore).
2016+
*
2017+
* Default implementation uses file-based operations via SystemConfigFileManager.
2018+
*
2019+
* @param application the dataspace application to update
2020+
* @param newModel the new RDF model to replace the existing named graph
2021+
* @throws IOException if an I/O error occurs
2022+
*/
2023+
public void updateDataspace(com.atomgraph.linkeddatahub.apps.model.Application application, Model newModel) throws IOException
2024+
{
2025+
if (application == null) throw new IllegalArgumentException("Application cannot be null");
2026+
if (newModel == null) throw new IllegalArgumentException("Model cannot be null");
2027+
2028+
synchronized (getContextDataset())
2029+
{
2030+
String dataspaceURI = application.getURI();
2031+
2032+
// Only support file-based URIs for the default implementation
2033+
if (!getContextDatasetURI().isAbsolute() || !"file".equals(getContextDatasetURI().getScheme()))
2034+
{
2035+
throw new UnsupportedOperationException("Only file-based context dataset URIs are supported for updates in default implementation");
2036+
}
2037+
2038+
Path configFilePath = Paths.get(getContextDatasetURI());
2039+
2040+
// Update the named graph in the dataset
2041+
getContextDataset().removeNamedModel(dataspaceURI).
2042+
addNamedModel(dataspaceURI, newModel);
2043+
2044+
// Write the updated dataset back to file
2045+
com.atomgraph.linkeddatahub.server.util.SystemConfigFileManager.writeDataset(getContextDataset(), configFilePath);
2046+
2047+
if (log.isInfoEnabled()) log.info("Updated dataspace <{}> in file: {}", dataspaceURI, configFilePath);
2048+
}
19932049
}
19942050

19952051
/**
19962052
* Returns true if configured to invalidate HTTP proxy cache of triplestore results.
1997-
*
2053+
*
19982054
* @return true if invalidated
19992055
*/
20002056
public boolean isInvalidateCache()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright 2025 Martynas Jusevičius <martynas@atomgraph.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.atomgraph.linkeddatahub.server.util;
18+
19+
import java.io.FileOutputStream;
20+
import java.io.IOException;
21+
import java.nio.file.Path;
22+
import org.apache.jena.query.Dataset;
23+
import org.apache.jena.rdf.model.Model;
24+
import org.apache.jena.riot.RDFDataMgr;
25+
import org.apache.jena.riot.RDFFormat;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
/**
30+
* Utility class for managing system configuration files in TriG format.
31+
* Provides methods for updating and writing configuration datasets.
32+
*
33+
* @author Martynas Jusevičius {@literal <martynas@atomgraph.com>}
34+
*/
35+
public class SystemConfigFileManager
36+
{
37+
38+
private static final Logger log = LoggerFactory.getLogger(SystemConfigFileManager.class);
39+
40+
/**
41+
* Writes a dataset to a file in TriG format.
42+
*
43+
* @param dataset the dataset to write
44+
* @param filePath the path to the output file
45+
* @throws IOException if an I/O error occurs during writing
46+
*/
47+
public static void writeDataset(Dataset dataset, Path filePath) throws IOException
48+
{
49+
if (dataset == null) throw new IllegalArgumentException("Dataset cannot be null");
50+
if (filePath == null) throw new IllegalArgumentException("File path cannot be null");
51+
52+
try (FileOutputStream out = new FileOutputStream(filePath.toFile()))
53+
{
54+
RDFDataMgr.write(out, dataset, RDFFormat.TRIG_PRETTY);
55+
if (log.isDebugEnabled()) log.debug("Wrote dataset to file: {}", filePath);
56+
}
57+
}
58+
59+
}

0 commit comments

Comments
 (0)