diff --git a/Dockerfile b/Dockerfile index e5df99152..9ce4ec936 100644 --- a/Dockerfile +++ b/Dockerfile @@ -89,7 +89,7 @@ ENV SIGN_UP_CERT_VALIDITY= ENV LOAD_DATASETS= -ENV CONTEXT_DATASET_URL=file:///var/linkeddatahub/datasets/system.trig +ENV CONTEXT_ENDPOINT_URL= ENV ADMIN_DATASET_URL=file:///var/linkeddatahub/datasets/admin.trig @@ -161,12 +161,6 @@ COPY platform/root-owner-authorization.trig.template root-owner-authorization.tr COPY platform/namespace-ontology.trig.template namespace-ontology.trig.template -# copy default datasets - -COPY platform/datasets/admin.trig /var/linkeddatahub/datasets/admin.trig - -COPY platform/datasets/end-user.trig /var/linkeddatahub/datasets/end-user.trig - # copy sitemap query & stylesheet COPY platform/sitemap/sitemap.rq.template /var/linkeddatahub/sitemap/sitemap.rq.template diff --git a/config/system.trig b/config/system.trig deleted file mode 100644 index 647f582c7..000000000 --- a/config/system.trig +++ /dev/null @@ -1,54 +0,0 @@ -@prefix lapp: . -@prefix ldh: . -@prefix a: . -@prefix ac: . -@prefix rdf: . -@prefix rdfs: . -@prefix xsd: . -@prefix ldt: . -@prefix sd: . -@prefix dct: . -@prefix foaf: . - -### do not use blank nodes to identify resources! ### -### urn: URI scheme is used because applications/services are not accessible in their own dataspace (under $BASE_URI) ### - -# root admin - - a lapp:Application, lapp:AdminApplication ; - dct:title "LinkedDataHub admin" ; - # ldt:base ; - ldh:origin ; - ldt:ontology ; - ldt:service ; - ac:stylesheet ; - lapp:endUserApplication ; - lapp:frontendProxy . - - a sd:Service ; - dct:title "LinkedDataHub admin service" ; - sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; - sd:endpoint ; - a:graphStore ; - a:quadStore ; - lapp:backendProxy . - -# root end-user - - a lapp:Application, lapp:EndUserApplication ; - dct:title "LinkedDataHub" ; - # ldt:base ; - ldh:origin ; - ldt:ontology ; - ldt:service ; - lapp:adminApplication ; - lapp:frontendProxy ; - lapp:public true . - - a sd:Service ; - dct:title "LinkedDataHub service" ; - sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; - sd:endpoint ; - a:graphStore ; - a:quadStore ; - lapp:backendProxy . diff --git a/docker-compose.yml b/docker-compose.yml index 7b8fffd36..5bcac1a60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,6 +55,7 @@ services: - HTTP_REDIRECT_PORT=${HTTPS_PORT} - HTTPS_PROXY_PORT=${HTTPS_PORT} - HTTPS=false + - CONTEXT_ENDPOINT_URL=http://fuseki-end-user:3030/ds/ - SELF_SIGNED_CERT=true # only on localhost - SIGN_UP_CERT_VALIDITY=180 - MAX_CONTENT_LENGTH=${MAX_CONTENT_LENGTH:-2097152} @@ -93,7 +94,8 @@ services: - ./datasets/secretary:/var/linkeddatahub/datasets/secretary - ./uploads:/var/www/linkeddatahub/uploads - ./config/dev.log4j.properties:/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/log4j.properties:ro - - ./config/system.trig:/var/linkeddatahub/datasets/system.trig:ro + - ./platform/datasets/end-user.trig:/var/linkeddatahub/datasets/end-user.trig:ro + - ./platform/datasets/admin.trig:/var/linkeddatahub/datasets/admin.trig:ro fuseki-admin: image: atomgraph/fuseki:4.7.0 user: root # otherwise fuseki user does not have permissions to the mounted folder which is owner by root diff --git a/platform/context.xsl b/platform/context.xsl index 1c4b4bd78..2202402b3 100644 --- a/platform/context.xsl +++ b/platform/context.xsl @@ -32,7 +32,7 @@ xmlns:orcid="&orcid;" - + @@ -108,8 +108,8 @@ xmlns:orcid="&orcid;" - - + + diff --git a/platform/datasets/end-user.trig b/platform/datasets/end-user.trig index 4c3574b08..36700347b 100644 --- a/platform/datasets/end-user.trig +++ b/platform/datasets/end-user.trig @@ -1,9 +1,12 @@ @prefix def: . @prefix ldh: . -@prefix ac: . +@prefix lapp: . +@prefix a: . +@prefix ac: . @prefix rdf: . @prefix xsd: . -@prefix dh: . +@prefix dh: . +@prefix ldt: . @prefix sd: . @prefix sp: . @prefix sioc: . @@ -421,4 +424,302 @@ WHERE sd:endpoint ; sd:supportedLanguage sd:SPARQL11Query . +} + +# APPLICATIONS & SERVICES (merged from config/system.trig) + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "LinkedDataHub" ; + foaf:primaryTopic . + + a lapp:Application, lapp:EndUserApplication ; + dct:title "LinkedDataHub" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + lapp:adminApplication ; + lapp:frontendProxy ; + lapp:public true . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Root admin" ; + foaf:primaryTopic . + + a lapp:Application, lapp:AdminApplication ; + dct:title "Root admin" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + ac:stylesheet ; + lapp:endUserApplication ; + lapp:frontendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Root service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "Root service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "LinkedDataHub admin service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "LinkedDataHub admin service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Papers" ; + foaf:primaryTopic . + + a lapp:Application, lapp:EndUserApplication ; + dct:title "Papers" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + lapp:adminApplication ; + lapp:frontendProxy ; + lapp:public true . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Papers admin" ; + foaf:primaryTopic . + + a lapp:Application, lapp:AdminApplication ; + dct:title "Papers admin" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + ac:stylesheet ; + lapp:endUserApplication ; + lapp:frontendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Papers service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "Papers service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Papers admin service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "Papers admin service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "SWIB" ; + foaf:primaryTopic . + + a lapp:Application, lapp:EndUserApplication ; + dct:title "SWIB" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + lapp:adminApplication ; + lapp:frontendProxy ; + lapp:public true . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "SWIB admin" ; + foaf:primaryTopic . + + a lapp:Application, lapp:AdminApplication ; + dct:title "SWIB admin" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + ac:stylesheet ; + lapp:endUserApplication ; + lapp:frontendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "SWIB service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "SWIB service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "SWIB admin service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "SWIB admin service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Portal" ; + foaf:primaryTopic . + + a lapp:Application, lapp:EndUserApplication ; + dct:title "Portal" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + lapp:adminApplication ; + lapp:frontendProxy ; + lapp:public true . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Portal admin" ; + foaf:primaryTopic . + + a lapp:Application, lapp:AdminApplication ; + dct:title "Portal admin" ; + ldh:origin ; + ldt:ontology ; + ldt:service ; + ac:stylesheet ; + lapp:endUserApplication ; + lapp:frontendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Portal service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "Portal service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + +} + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Portal admin service" ; + foaf:primaryTopic . + + a sd:Service ; + dct:title "Portal admin service" ; + sd:supportedLanguage sd:SPARQL11Query, sd:SPARQL11Update ; + sd:endpoint ; + a:graphStore ; + a:quadStore ; + lapp:backendProxy . + } \ No newline at end of file diff --git a/platform/entrypoint.sh b/platform/entrypoint.sh index 6a4bb2e6d..19c2a6b27 100755 --- a/platform/entrypoint.sh +++ b/platform/entrypoint.sh @@ -181,8 +181,8 @@ if [ -z "$SIGN_UP_CERT_VALIDITY" ]; then exit 1 fi -if [ -z "$CONTEXT_DATASET_URL" ]; then - echo '$CONTEXT_DATASET_URL not set' +if [ -z "$CONTEXT_ENDPOINT_URL" ]; then + echo '$CONTEXT_ENDPOINT_URL not set' exit 1 fi @@ -509,29 +509,26 @@ SECRETARY_KEY_URI="${SECRETARY_KEY_DOC_URI}#this" # Note: LOAD_DATASETS check is now done per-app inside the loop -# base the $CONTEXT_DATASET +# extract app metadata from the end-user dataset for entrypoint initialization -webapp_context_dataset="/WEB-INF/classes/com/atomgraph/linkeddatahub/system.nq" -based_context_dataset="${PWD}/webapps/ROOT${webapp_context_dataset}" +based_end_user_dataset=$(mktemp --suffix=.nq) -case "$CONTEXT_DATASET_URL" in +case "$END_USER_DATASET_URL" in "file://"*) - CONTEXT_DATASET=$(echo "$CONTEXT_DATASET_URL" | cut -c 8-) # strip leading file:// + END_USER_DATASET=$(echo "$END_USER_DATASET_URL" | cut -c 8-) # strip leading file:// - printf "\n### Reading context dataset from a local file: %s\n" "$CONTEXT_DATASET" ;; - *) - CONTEXT_DATASET=$(mktemp) + printf "\n### Reading end-user dataset from a local file for app extraction: %s\n" "$END_USER_DATASET" ;; + *) + END_USER_DATASET=$(mktemp) - printf "\n### Downloading context dataset from a URL: %s\n" "$CONTEXT_DATASET_URL" + printf "\n### Downloading end-user dataset from a URL for app extraction: %s\n" "$END_USER_DATASET_URL" - curl "$CONTEXT_DATASET_URL" > "$CONTEXT_DATASET" ;; + curl "$END_USER_DATASET_URL" > "$END_USER_DATASET" ;; esac -trig --base="$BASE_URI" "$CONTEXT_DATASET" > "$based_context_dataset" +trig --base="$BASE_URI" --output=nq "$END_USER_DATASET" > "$based_end_user_dataset" -sparql --data="$based_context_dataset" --query="select-root-services.rq" --results=XML > root_service_metadata.xml - -# extract app metadata from the system dataset using SPARQL and XPath queries +sparql --data="$based_end_user_dataset" --query="select-root-services.rq" --results=XML > root_service_metadata.xml readarray apps < <(xmlstarlet sel -B \ -N srx="http://www.w3.org/2005/sparql-results#" \ @@ -625,10 +622,10 @@ for app in "${apps[@]}"; do # append ownership metadata to apps if it's not present (apps have to be URI resources!) if [ -z "$end_user_owner" ]; then - echo "<${end_user_app}> <${OWNER_URI}> ." >> "$based_context_dataset" + echo "<${end_user_app}> <${OWNER_URI}> ." >> "$based_end_user_dataset" fi if [ -z "$admin_owner" ]; then - echo "<${admin_app}> <${OWNER_URI}> ." >> "$based_context_dataset" + echo "<${admin_app}> <${OWNER_URI}> ." >> "$based_end_user_dataset" fi printf "\n### Quad store URL of the root end-user service: %s\n" "$end_user_quad_store_url" @@ -848,7 +845,7 @@ CLIENT_KEYSTORE_PASSWORD_PARAM="--stringparam ldhc:clientKeyStorePassword '$CLIE CLIENT_TRUSTSTORE_PASSWORD_PARAM="--stringparam ldhc:clientTrustStorePassword '$CLIENT_TRUSTSTORE_PASSWORD' " UPLOAD_ROOT_PARAM="--stringparam ldhc:uploadRoot 'file://$UPLOAD_ROOT' " SIGN_UP_CERT_VALIDITY_PARAM="--stringparam ldhc:signUpCertValidity '$SIGN_UP_CERT_VALIDITY' " -CONTEXT_DATASET_PARAM="--stringparam ldhc:contextDataset '$webapp_context_dataset' " +CONTEXT_ENDPOINT_PARAM="--stringparam ldhc:contextEndpoint '$CONTEXT_ENDPOINT_URL' " MAIL_SMTP_HOST_PARAM="--stringparam mail.smtp.host '$MAIL_SMTP_HOST' " MAIL_SMTP_PORT_PARAM="--stringparam mail.smtp.port '$MAIL_SMTP_PORT' " MAIL_USER_PARAM="--stringparam mail.user '$MAIL_USER' " @@ -967,7 +964,7 @@ transform="xsltproc \ $CLIENT_TRUSTSTORE_PASSWORD_PARAM \ $UPLOAD_ROOT_PARAM \ $SIGN_UP_CERT_VALIDITY_PARAM \ - $CONTEXT_DATASET_PARAM \ + $CONTEXT_ENDPOINT_PARAM \ $AUTH_QUERY_PARAM \ $OWNER_AUTH_QUERY_PARAM \ $ENABLE_LINKED_DATA_PROXY_PARAM \ diff --git a/platform/select-root-services.rq b/platform/select-root-services.rq index 2a307e4e1..d8d7a02c8 100644 --- a/platform/select-root-services.rq +++ b/platform/select-root-services.rq @@ -7,31 +7,44 @@ PREFIX foaf: SELECT ?endUserApp ?endUserOrigin ?endUserQuadStore ?endUserEndpoint ?endUserAuthUser ?endUserAuthPwd ?endUserMaker ?adminApp ?adminOrigin ?adminQuadStore ?adminEndpoint ?adminAuthUser ?adminAuthPwd ?adminMaker { - ?endUserApp ldh:origin ?endUserOrigin ; - ldt:service ?endUserService ; - lapp:adminApplication ?adminApp . - ?adminApp ldt:service ?adminService ; - ldh:origin ?adminOrigin . - ?endUserService a:quadStore ?endUserQuadStore ; - sd:endpoint ?endUserEndpoint . - ?adminService a:quadStore ?adminQuadStore ; - sd:endpoint ?adminEndpoint . - OPTIONAL - { - ?endUserService a:authUser ?endUserAuthUser ; - a:authPwd ?endUserAuthPwd . - } - OPTIONAL - { - ?adminService a:authUser ?adminAuthUser ; - a:authPwd ?adminAuthPwd . - } - OPTIONAL - { - ?endUserService foaf:maker ?endUserMaker - } - OPTIONAL - { - ?adminService foaf:maker ?adminMaker + GRAPH ?endUserAppGraph { + ?endUserApp ldh:origin ?endUserOrigin ; + ldt:service ?endUserService ; + lapp:adminApplication ?adminApp . + + GRAPH ?endUserServiceGraph { + ?endUserService a:quadStore ?endUserQuadStore ; + sd:endpoint ?endUserEndpoint . + + OPTIONAL + { + ?endUserService a:authUser ?endUserAuthUser ; + a:authPwd ?endUserAuthPwd . + } + OPTIONAL + { + ?endUserService foaf:maker ?endUserMaker + } + } + + GRAPH ?adminAppGraph { + ?adminApp ldt:service ?adminService ; + ldh:origin ?adminOrigin . + + GRAPH ?adminServiceGraph { + ?adminService a:quadStore ?adminQuadStore ; + sd:endpoint ?adminEndpoint . + + OPTIONAL + { + ?adminService a:authUser ?adminAuthUser ; + a:authPwd ?adminAuthPwd . + } + OPTIONAL + { + ?adminService foaf:maker ?adminMaker + } + } + } } -} \ No newline at end of file +} diff --git a/src/main/java/com/atomgraph/linkeddatahub/Application.java b/src/main/java/com/atomgraph/linkeddatahub/Application.java index eeebe124a..f831fb989 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/Application.java +++ b/src/main/java/com/atomgraph/linkeddatahub/Application.java @@ -33,6 +33,7 @@ import jakarta.annotation.PostConstruct; import jakarta.servlet.ServletConfig; import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFFormat; import org.apache.jena.riot.RDFWriterRegistry; @@ -41,6 +42,7 @@ import com.atomgraph.client.util.DataManagerImpl; import com.atomgraph.client.vocabulary.AC; import com.atomgraph.client.writer.function.UUID; +import com.atomgraph.core.client.SPARQLClient; import com.atomgraph.core.exception.ConfigurationException; import com.atomgraph.core.io.DatasetProvider; import com.atomgraph.core.io.ModelProvider; @@ -162,6 +164,7 @@ import javax.xml.transform.Source; import org.apache.jena.ontology.Ontology; import org.apache.jena.query.Dataset; +import org.apache.jena.query.ParameterizedSparqlString; import org.apache.jena.query.Query; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryFactory; @@ -219,14 +222,12 @@ import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpCoreContext; import org.apache.jena.query.DatasetFactory; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.ResIterator; import org.apache.jena.rdf.model.Resource; import org.apache.jena.riot.resultset.ResultSetLang; import org.apache.jena.riot.system.ErrorHandlerFactory; import org.apache.jena.riot.system.ParserProfile; import org.apache.jena.riot.system.RiotLib; -import org.apache.jena.sparql.graph.GraphReadOnly; import org.apache.jena.vocabulary.DCTerms; import org.apache.jena.vocabulary.LocationMappingVocab; import org.apache.jena.vocabulary.RDF; @@ -290,8 +291,9 @@ public class Application extends ResourceConfig private final boolean enableWebIDSignUp; private final String oidcRefreshTokensPropertiesPath; private final Properties oidcRefreshTokens; - - private Dataset contextDataset; + private final String contextEndpoint; + private final Query applicationQuery; + private final Query datasetQuery; /** * Constructs system application and configures it using sevlet config. @@ -342,6 +344,9 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti servletConfig.getServletContext().getInitParameter(LDHC.supportedLanguages.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.supportedLanguages.getURI()) : null, servletConfig.getServletContext().getInitParameter(LDHC.enableWebIDSignUp.getURI()) != null ? Boolean.parseBoolean(servletConfig.getServletContext().getInitParameter(LDHC.enableWebIDSignUp.getURI())) : true, servletConfig.getServletContext().getInitParameter(LDHC.oidcRefreshTokens.getURI()), + servletConfig.getServletContext().getInitParameter(LDHC.contextEndpoint.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.contextEndpoint.getURI()) : null, + servletConfig.getServletContext().getInitParameter(LDHC.applicationQuery.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.applicationQuery.getURI()) : null, + servletConfig.getServletContext().getInitParameter(LDHC.datasetQuery.getURI()) != null ? servletConfig.getServletContext().getInitParameter(LDHC.datasetQuery.getURI()) : null, servletConfig.getServletContext().getInitParameter("mail.user") != null ? servletConfig.getServletContext().getInitParameter("mail.user") : null, servletConfig.getServletContext().getInitParameter("mail.password") != null ? servletConfig.getServletContext().getInitParameter("mail.password") : null, servletConfig.getServletContext().getInitParameter("mail.smtp.host") != null ? servletConfig.getServletContext().getInitParameter("mail.smtp.host") : null, @@ -351,14 +356,6 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti servletConfig.getServletContext().getInitParameter(ORCID.clientID.getURI()) != null ? servletConfig.getServletContext().getInitParameter(ORCID.clientID.getURI()) : null, servletConfig.getServletContext().getInitParameter(ORCID.clientSecret.getURI()) != null ? servletConfig.getServletContext().getInitParameter(ORCID.clientSecret.getURI()) : null ); - - URI contextDatasetURI = servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI()) != null ? new URI(servletConfig.getServletContext().getInitParameter(LDHC.contextDataset.getURI())) : null; - if (contextDatasetURI == null) - { - if (log.isErrorEnabled()) log.error("Context dataset URI '{}' not configured", LDHC.contextDataset.getURI()); - throw new ConfigurationException(LDHC.contextDataset); - } - this.contextDataset = getDataset(servletConfig.getServletContext(), contextDatasetURI); } /** @@ -403,6 +400,9 @@ public Application(@Context ServletConfig servletConfig) throws URISyntaxExcepti * @param supportedLanguageCodes comma-separated codes of supported languages * @param enableWebIDSignUp true if WebID signup is enabled * @param oidcRefreshTokensPropertiesPath path to the properties file with OIDC refresh tokens + * @param contextEndpointURI SPARQL endpoint to load the application and dataset RDF metadata from + * @param applicationQueryString SPARQL query string that returns configured applications + * @param datasetQueryString SPARQL query string that returns configured datasets * @param mailUser username of the SMTP email server * @param mailPassword password of the SMTP email server * @param smtpHost hostname of the SMTP email server @@ -425,6 +425,7 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType final Integer cookieMaxAge, final boolean enableLinkedDataProxy, final Integer maxContentLength, final Integer maxConnPerRoute, final Integer maxTotalConn, final Integer maxRequestRetries, final Integer maxImportThreads, final String notificationAddressString, final String supportedLanguageCodes, final boolean enableWebIDSignUp, final String oidcRefreshTokensPropertiesPath, + final String contextEndpointURI, final String applicationQueryString, final String datasetQueryString, final String mailUser, final String mailPassword, final String smtpHost, final String smtpPort, final String googleClientID, final String googleClientSecret, final String orcidClientID, final String orcidClientSecret) @@ -538,6 +539,26 @@ public Application(final ServletConfig servletConfig, final MediaTypes mediaType } this.supportedLanguages = Arrays.asList(supportedLanguageCodes.split(",")).stream().map(code -> Locale.forLanguageTag(code)).collect(Collectors.toList()); + if (contextEndpointURI == null) + { + if (log.isErrorEnabled()) log.error("Context endpoint URI '{}' not configured", LDHC.contextEndpoint.getURI()); + throw new ConfigurationException(LDHC.contextEndpoint); + } + this.contextEndpoint = contextEndpointURI; + + if (applicationQueryString == null) + { + if (log.isErrorEnabled()) log.error("Application SPARQL query is not configured properly"); + throw new ConfigurationException(LDHC.applicationQuery); + } + this.applicationQuery = QueryFactory.create(applicationQueryString); + if (datasetQueryString == null) + { + if (log.isErrorEnabled()) log.error("Dataset SPARQL query is not configured properly"); + throw new ConfigurationException(LDHC.datasetQuery); + } + this.datasetQuery = QueryFactory.create(datasetQueryString); + this.servletConfig = servletConfig; this.mediaTypes = mediaTypes; this.maxGetRequestSize = maxGetRequestSize; @@ -1234,13 +1255,13 @@ public void handleAuthorizationCreated(AuthorizationCreated event) throws Messag /** * Matches application by type and request URL. - * + * * @param absolutePath request URL without the query string * @return app resource or null, if none matched */ public Resource matchApp(URI absolutePath) { - return getAppByOrigin(getContextModel(), LAPP.Application, absolutePath); // make sure we return an immutable model + return getAppByOrigin(LAPP.Application, absolutePath); } /** @@ -1285,42 +1306,53 @@ public static String normalizeOrigin(URI uri) } /** - * Finds application by origin matching from the application model. + * Finds application by origin matching by querying the context SPARQL endpoint. * Applications are filtered by type first. * - * @param model application model * @param type application type * @param absolutePath request URL (without the query string) * @return app resource or null if no match found */ - public Resource getAppByOrigin(Model model, Resource type, URI absolutePath) + public Resource getAppByOrigin(Resource type, URI absolutePath) { - if (model == null) throw new IllegalArgumentException("Model cannot be null"); if (type == null) throw new IllegalArgumentException("Resource cannot be null"); if (absolutePath == null) throw new IllegalArgumentException("URI cannot be null"); String requestOrigin = normalizeOrigin(absolutePath); - ResIterator it = model.listSubjectsWithProperty(RDF.type, type); - try + // Build parameterized query from the configured application query + ParameterizedSparqlString pss = new ParameterizedSparqlString(getApplicationQuery().toString()); + pss.setIri(RDF.type.getLocalName(), type.getURI()); + + SPARQLClient sparqlClient = SPARQLClient.create(getMediaTypes(), getClient().target(getContextEndpoint())); + try (Response cr = sparqlClient.query(pss.asQuery(), Model.class)) { - while (it.hasNext()) - { - Resource app = it.next(); + Model model = cr.readEntity(Model.class); - // Use origin-based matching - return immediately on match since origins are unique - if (app.hasProperty(LDH.origin)) + // Iterate through the results and match by normalized origin + ResIterator it = model.listSubjectsWithProperty(RDF.type, type); + try + { + while (it.hasNext()) { + Resource app = it.next(); + + if (!app.hasProperty(LDH.origin)) + { + if (log.isErrorEnabled()) log.error("Application with URI <'{}'> is missing a <{}> value", app.getURI(), LDH.origin); + throw new ConfigurationException(LDH.origin); + } + URI appOriginURI = URI.create(app.getPropertyResourceValue(LDH.origin).getURI()); String normalizedAppOrigin = normalizeOrigin(appOriginURI); if (requestOrigin.equals(normalizedAppOrigin)) return app; } } - } - finally - { - it.close(); + finally + { + it.close(); + } } return null; @@ -1335,7 +1367,19 @@ public Resource getAppByOrigin(Model model, Resource type, URI absolutePath) */ public Resource matchDataset(Resource type, URI absolutePath) { - return matchDataset(getContextModel(), type, absolutePath); // make sure we return an immutable model + if (type == null) throw new IllegalArgumentException("Resource cannot be null"); + if (absolutePath == null) throw new IllegalArgumentException("URI cannot be null"); + + // Build parameterized query from the configured dataset query + ParameterizedSparqlString pss = new ParameterizedSparqlString(getDatasetQuery().toString()); + pss.setIri(RDF.type.getLocalName(), type.getURI()); + + SPARQLClient sparqlClient = SPARQLClient.create(getMediaTypes(), getClient().target(getContextEndpoint())); + try (Response cr = sparqlClient.query(pss.asQuery(), Model.class)) + { + Model model = cr.readEntity(Model.class); + return getLongestURIResource(getLengthMap(getRelativeDatasets(model, type, absolutePath))); + } } /** @@ -1941,23 +1985,23 @@ public URI getUploadRoot() } /** - * Returns RDF dataset with LinkedDataHub application descriptions. - * - * @return RDF dataset + * Returns the context SPARQL endpoint URL for querying application metadata. + * + * @return SPARQL endpoint URL */ - protected Dataset getContextDataset() + protected String getContextEndpoint() { - return contextDataset; + return contextEndpoint; } - /** - * Returns RDF model with LinkedDataHub application descriptions. - * - * @return RDF model - */ - public Model getContextModel() + protected Query getApplicationQuery() + { + return applicationQuery; + } + + protected Query getDatasetQuery() { - return ModelFactory.createModelForGraph(new GraphReadOnly(getContextDataset().getDefaultModel().getGraph())); + return datasetQuery; } /** diff --git a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java index 17eca3f42..458253106 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java +++ b/src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDHC.java @@ -137,8 +137,14 @@ public static String getURI() /** Signup cert validity property */ public static final DatatypeProperty signUpCertValidity = m_model.createDatatypeProperty( NS + "signUpCertValidity" ); - /** Context dataset property */ - public static final ObjectProperty contextDataset = m_model.createObjectProperty( NS + "contextDataset" ); + /** Context endpoint property */ + public static final ObjectProperty contextEndpoint = m_model.createObjectProperty( NS + "contextEndpoint" ); + + /** Application query property */ + public static final DatatypeProperty applicationQuery = m_model.createDatatypeProperty( NS + "applicationQuery" ); + + /** Dataset query property */ + public static final DatatypeProperty datasetQuery = m_model.createDatatypeProperty( NS + "datasetQuery" ); /** Max connections per route property */ public static final DatatypeProperty maxConnPerRoute = m_model.createDatatypeProperty( NS + "maxConnPerRoute" ); diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index a31768cc5..6211facd1 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -200,8 +200,8 @@ SELECT * -CONSTRUCT - { +CONSTRUCT + { ?s ?p ?o . } WHERE @@ -215,6 +215,32 @@ WHERE { ?s ?p ?o } } } +]]> + + + https://w3id.org/atomgraph/linkeddatahub/config#applicationQuery + + +DESCRIBE ?app +WHERE { + GRAPH ?g { + ?app ldh:origin ?origin . + } +} +]]> + + + https://w3id.org/atomgraph/linkeddatahub/config#datasetQuery + + +DESCRIBE ?dataset +WHERE { + GRAPH ?g { + ?dataset lapp:prefix ?prefix . + } +} ]]>