diff --git a/.github/workflows/build11.yml b/.github/workflows/build11.yml
new file mode 100644
index 00000000000..a0a0012d20d
--- /dev/null
+++ b/.github/workflows/build11.yml
@@ -0,0 +1,14 @@
+name: succed job 'build (11)'
+
+on: pull_request
+
+jobs:
+ build:
+ strategy:
+ fail-fast: true
+ matrix:
+ jdk: [ 11 ]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Always Succeed
+ run: true
\ No newline at end of file
diff --git a/.github/workflows/develop-status.yml b/.github/workflows/develop-status.yml
index cb782abfc29..32d63b0a6f9 100644
--- a/.github/workflows/develop-status.yml
+++ b/.github/workflows/develop-status.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- jdk: [11, 17]
+ jdk: [17]
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/main-status.yml b/.github/workflows/main-status.yml
index ba8fd33f39c..5fdf7c63427 100644
--- a/.github/workflows/main-status.yml
+++ b/.github/workflows/main-status.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- jdk: [11, 17]
+ jdk: [17]
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/pr-verify.yml b/.github/workflows/pr-verify.yml
index 29edc6b64e3..6648a34ee5a 100644
--- a/.github/workflows/pr-verify.yml
+++ b/.github/workflows/pr-verify.yml
@@ -19,7 +19,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
cache: maven
- name: Check formatting
@@ -37,7 +37,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
cache: maven
- name: Compile (mvn clean install)
@@ -50,7 +50,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- jdk: [ 11, 25 ]
+ jdk: [ 17, 25 ]
steps:
- uses: actions/checkout@v4
- name: Set up JDK
@@ -78,7 +78,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
cache: maven
- name: Build
@@ -99,7 +99,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
cache: maven
- name: Build
@@ -120,7 +120,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
cache: maven
- name: Run install
@@ -137,7 +137,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
cache: maven
- name: Install dependencies
diff --git a/pom.xml b/pom.xml
index 06c64e89308..1748eb7e057 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@
coretoolsspring-components
+ spring6-componentstestsuitescomplianceexamples
@@ -806,6 +807,22 @@
solr-core${solr.version}
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
+
+ ch.qos.logback
+ logback-core
+ ${logback.version}
+
@@ -1039,6 +1056,8 @@
javax.servlet:jstl:*:*:providedjavax.servlet:javax.servlet-api:*:*:test
+
+ javax.*:*:*:*:provided
@@ -1061,6 +1080,8 @@
org.opentest4j.*
+
+ providedtrue
@@ -1072,7 +1093,7 @@
org.codehaus.mojoextra-enforcer-rules
- 1.6.1
+ 1.11.0
diff --git a/site/content/documentation/programming/spring.md b/site/content/documentation/programming/spring.md
index b6b6b99c764..35a1a2c18d4 100644
--- a/site/content/documentation/programming/spring.md
+++ b/site/content/documentation/programming/spring.md
@@ -17,11 +17,11 @@ To use RDF as the data backend of a spring application built with maven, use the
```xml
org.eclipse.rdf4j
- rdf4j-spring
+ rdf4j-spring6${rdf4j.version}
```
-... setting the property `rdf4j.version` is set to the RDF4J version you want (minimum `4.0.0`).
+... setting the property `rdf4j.version` is set to the RDF4J version you want (minimum `5.3.0`).
In order for the application to run, a repository has to be configured:
diff --git a/spring-components/rdf4j-spring-demo/pom.xml b/spring-components/rdf4j-spring-demo/pom.xml
index 011ac7048d6..192ad0d30ce 100644
--- a/spring-components/rdf4j-spring-demo/pom.xml
+++ b/spring-components/rdf4j-spring-demo/pom.xml
@@ -36,6 +36,12 @@
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+ provided
+
diff --git a/spring-components/rdf4j-spring/pom.xml b/spring-components/rdf4j-spring/pom.xml
index 8b422bc394d..13aa8666311 100644
--- a/spring-components/rdf4j-spring/pom.xml
+++ b/spring-components/rdf4j-spring/pom.xml
@@ -80,6 +80,13 @@
mockserver-junit-jupiter-no-dependenciestest
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+ provided
+
+
diff --git a/spring6-components/pom.xml b/spring6-components/pom.xml
new file mode 100644
index 00000000000..bca4a50b348
--- /dev/null
+++ b/spring6-components/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ org.eclipse.rdf4j
+ rdf4j
+ 5.3.0-SNAPSHOT
+
+ pom
+
+ spring6-boot-sparql-web
+ rdf4j-spring6
+ rdf4j-spring6-demo
+
+
+
+ 17
+ 3.5.7
+
+ 6.2.12
+ 2.0.17
+ 1.5.20
+ 2.24.3
+ 12.0.29
+
+ rdf4j-spring6-components
+ RDF4J: Spring6 components
+ Components to use with Spring
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+ org.springframework
+ spring-test
+ test
+
+
+ org.springframework
+ spring-jcl
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+
diff --git a/spring6-components/rdf4j-spring6-demo/README.md b/spring6-components/rdf4j-spring6-demo/README.md
new file mode 100644
index 00000000000..a25c631f137
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/README.md
@@ -0,0 +1,14 @@
+# RDF4J-Spring Demo
+
+Small demo application for `rdf4j-spring`.
+
+The purpose of `rdf4j-spring` is to use an RDF4J repository as the data backend of a spring or spring boot application.
+
+To run the demo, do
+
+```$bash
+mvn spring-boot:run
+```
+
+The program writes to stdout and exits. The class [ArtDemoCli](src/main/java/org/eclipse/rdf4j/spring.demo/ArtDemoCli.java) is a good starting point for looking at the code.
+
diff --git a/spring6-components/rdf4j-spring6-demo/pom.xml b/spring6-components/rdf4j-spring6-demo/pom.xml
new file mode 100644
index 00000000000..697e62e0e60
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/pom.xml
@@ -0,0 +1,58 @@
+
+
+ 4.0.0
+ rdf4j-spring6-demo
+ RDF4J: Spring6 Demo
+ Demo of a spring-boot project using an RDF4J repo as its backend
+
+ org.eclipse.rdf4j
+ rdf4j-spring6-components
+ 5.3.0-SNAPSHOT
+
+
+
+ org.eclipse.rdf4j
+ rdf4j-spring6
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework
+ spring-jcl
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/ArtDemoCli.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/ArtDemoCli.java
new file mode 100644
index 00000000000..537f39b0535
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/ArtDemoCli.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.spring.demo.model.Artist;
+import org.eclipse.rdf4j.spring.demo.model.Painting;
+import org.eclipse.rdf4j.spring.demo.service.ArtService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Command line interface for the demo. Takes no parameters. It outputs the content of the demo repository, then adds
+ * some data and outputs the content of the repository again.
+ *
+ * Accessing the repository is done via the {@link ArtService} class, which just encapsulates accesses to the
+ * {@link org.eclipse.rdf4j.spring.demo.dao.PaintingDao} and {@link org.eclipse.rdf4j.spring.demo.dao.ArtistDao}
+ * classes.
+ *
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@SpringBootApplication
+public class ArtDemoCli implements CommandLineRunner {
+ @Autowired
+ ArtService artService;
+
+ public static void main(String[] args) {
+ SpringApplication.run(ArtDemoCli.class, args).close();
+ }
+
+ @Override
+ public void run(String... args) {
+ System.out.println("\nData read from 'artists.ttl':");
+ Map> paintingsMap = artService.getPaintingsGroupedByArtist();
+ listPaintingsByArtist(paintingsMap);
+ System.out.println("\nNow adding some data...");
+ addPaintingWithArtist();
+ System.out.println("\nReloaded data:");
+ paintingsMap = artService.getPaintingsGroupedByArtist();
+ listPaintingsByArtist(paintingsMap);
+ System.out.println("\n");
+ listArtistsWithoutPaintings();
+ System.out.println("\n");
+ }
+
+ private void addPaintingWithArtist() {
+ Artist a = new Artist();
+ a.setFirstName("Jan");
+ a.setLastName("Vermeer");
+ IRI artistId = artService.addArtist(a);
+ Painting p = new Painting();
+ p.setTitle("View of Delft");
+ p.setTechnique("oil on canvas");
+ p.setArtistId(artistId);
+ artService.addPainting(p);
+ }
+
+ private void listPaintingsByArtist(Map> paintingsMap) {
+ for (Artist a : paintingsMap.keySet()) {
+ System.out.println(String.format("%s %s", a.getFirstName(), a.getLastName()));
+ for (Painting p : paintingsMap.get(a)) {
+ System.out.println(String.format("\t%s (%s)", p.getTitle(), p.getTechnique()));
+ }
+ }
+ }
+
+ private void listArtistsWithoutPaintings() {
+ System.out.println("Artists without paintings:");
+ Set a = artService.getArtistsWithoutPaintings();
+ if (a.isEmpty()) {
+ System.out.println("\t[none]");
+ } else {
+ for (Artist artist : a) {
+ System.out.println(String.format("%s %s", artist.getFirstName(), artist.getLastName()));
+ }
+ }
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/ArtDemoConfig.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/ArtDemoConfig.java
new file mode 100644
index 00000000000..d9dda6ca27d
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/ArtDemoConfig.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo;
+
+import org.eclipse.rdf4j.spring.RDF4JConfig;
+import org.eclipse.rdf4j.spring.dao.RDF4JDao;
+import org.eclipse.rdf4j.spring.demo.support.InitialDataInserter;
+import org.eclipse.rdf4j.spring.support.DataInserter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.*;
+import org.springframework.core.io.Resource;
+
+/**
+ * Spring config for the demo.
+ *
+ * Here is what it does:
+ *
+ *
+ *
it imports {@link RDF4JConfig} which interprets the config properties (in our example, they are in
+ * application.properties) and registers a number of beans.
+ *
it scans the org.eclipse.rdf4j.spring.demo.dao package, finds the DAOs, registers them as beans and
+ * injects their dependencies
+ *
it configures the 'data inserter' beans, which read data from the 'artists.ttl' file and adds them to the
+ * repository at startup
+ *
+ *
+ * See {@link org.eclipse.rdf4j.spring Rdf4J-Spring} for an overview and more pointers.
+ *
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@Configuration
+@Import(RDF4JConfig.class)
+@ComponentScan(
+ value = "org.eclipse.rdf4j.spring.demo.dao", includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = RDF4JDao.class))
+public class ArtDemoConfig {
+ @Bean
+ public DataInserter getDataInserter() {
+ return new DataInserter();
+ }
+
+ @Bean
+ public InitialDataInserter getInitialDataInserter(
+ @Autowired DataInserter dataInserter,
+ @Value("classpath:/artists.ttl") Resource ttlFile) {
+ return new InitialDataInserter(dataInserter, ttlFile);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/dao/ArtistDao.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/dao/ArtistDao.java
new file mode 100644
index 00000000000..39c8c1b5209
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/dao/ArtistDao.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.dao;
+
+import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.bound;
+import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.not;
+import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri;
+import static org.eclipse.rdf4j.spring.demo.model.Artist.ARTIST_FIRST_NAME;
+import static org.eclipse.rdf4j.spring.demo.model.Artist.ARTIST_ID;
+import static org.eclipse.rdf4j.spring.demo.model.Artist.ARTIST_LAST_NAME;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.vocabulary.FOAF;
+import org.eclipse.rdf4j.query.BindingSet;
+import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries;
+import org.eclipse.rdf4j.spring.dao.SimpleRDF4JCRUDDao;
+import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.MutableBindings;
+import org.eclipse.rdf4j.spring.dao.support.sparql.NamedSparqlSupplier;
+import org.eclipse.rdf4j.spring.demo.model.Artist;
+import org.eclipse.rdf4j.spring.demo.model.EX;
+import org.eclipse.rdf4j.spring.demo.model.Painting;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+import org.eclipse.rdf4j.spring.util.QueryResultUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * Class responsible for repository access for managing {@link Artist} entities.
+ *
+ * The class extends the {@link SimpleRDF4JCRUDDao}, providing capabilities for inserting and reading entities.
+ *
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@Component
+public class ArtistDao extends SimpleRDF4JCRUDDao {
+
+ public ArtistDao(RDF4JTemplate rdf4JTemplate) {
+ super(rdf4JTemplate);
+ }
+
+ @Override
+ protected void populateIdBindings(MutableBindings bindingsBuilder, IRI iri) {
+ bindingsBuilder.add(ARTIST_ID, iri);
+ }
+
+ @Override
+ protected void populateBindingsForUpdate(MutableBindings bindingsBuilder, Artist artist) {
+ bindingsBuilder
+ .add(ARTIST_FIRST_NAME, artist.getFirstName())
+ .add(ARTIST_LAST_NAME, artist.getLastName());
+ }
+
+ @Override
+ protected Artist mapSolution(BindingSet querySolution) {
+ Artist artist = new Artist();
+ artist.setId(QueryResultUtils.getIRI(querySolution, ARTIST_ID));
+ artist.setFirstName(QueryResultUtils.getString(querySolution, ARTIST_FIRST_NAME));
+ artist.setLastName(QueryResultUtils.getString(querySolution, ARTIST_LAST_NAME));
+ return artist;
+ }
+
+ @Override
+ protected String getReadQuery() {
+ return "prefix foaf: "
+ + "prefix ex: "
+ + "SELECT ?artist_id ?artist_firstName ?artist_lastName where {"
+ + "?artist_id a ex:Artist; "
+ + " foaf:firstName ?artist_firstName; "
+ + " foaf:surname ?artist_lastName ."
+ + " } ";
+ }
+
+ @Override
+ protected NamedSparqlSupplier getInsertSparql(Artist artist) {
+ return NamedSparqlSupplier.of("insert", () -> Queries.INSERT(ARTIST_ID.isA(iri(EX.Artist))
+ .andHas(iri(FOAF.FIRST_NAME), ARTIST_FIRST_NAME)
+ .andHas(iri(FOAF.SURNAME), ARTIST_LAST_NAME))
+ .getQueryString());
+ }
+
+ @Override
+ protected IRI getInputId(Artist artist) {
+ if (artist.getId() == null) {
+ return getRdf4JTemplate().getNewUUID();
+ }
+ return artist.getId();
+ }
+
+ static abstract class QUERY_KEYS {
+ public static final String ARTISTS_WITHOUT_PAINTINGS = "artists-without-paintings";
+ }
+
+ @Override
+ protected NamedSparqlSupplierPreparer prepareNamedSparqlSuppliers(NamedSparqlSupplierPreparer preparer) {
+ return preparer.forKey(QUERY_KEYS.ARTISTS_WITHOUT_PAINTINGS)
+ .supplySparql(Queries.SELECT(
+ ARTIST_ID)
+ .where(
+ ARTIST_ID.isA(iri(EX.Artist))
+ .and(ARTIST_ID.has(iri(EX.creatorOf), Painting.PAINTING_ID).optional())
+ .filter(not(bound(Painting.PAINTING_ID))))
+ .getQueryString()
+ );
+ }
+
+ public Set getArtistsWithoutPaintings() {
+ return getNamedTupleQuery(QUERY_KEYS.ARTISTS_WITHOUT_PAINTINGS)
+ .evaluateAndConvert()
+ .toStream()
+ .map(bs -> QueryResultUtils.getIRI(bs, ARTIST_ID))
+ .map(this::getById)
+ .collect(Collectors.toSet());
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/dao/PaintingDao.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/dao/PaintingDao.java
new file mode 100644
index 00000000000..14c044fb745
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/dao/PaintingDao.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.dao;
+
+import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri;
+import static org.eclipse.rdf4j.spring.demo.model.Painting.PAINTING_ARTIST_ID;
+import static org.eclipse.rdf4j.spring.demo.model.Painting.PAINTING_ID;
+import static org.eclipse.rdf4j.spring.demo.model.Painting.PAINTING_LABEL;
+import static org.eclipse.rdf4j.spring.demo.model.Painting.PAINTING_TECHNIQUE;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.vocabulary.RDFS;
+import org.eclipse.rdf4j.query.BindingSet;
+import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries;
+import org.eclipse.rdf4j.spring.dao.RDF4JDao;
+import org.eclipse.rdf4j.spring.dao.SimpleRDF4JCRUDDao;
+import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.MutableBindings;
+import org.eclipse.rdf4j.spring.dao.support.sparql.NamedSparqlSupplier;
+import org.eclipse.rdf4j.spring.demo.model.EX;
+import org.eclipse.rdf4j.spring.demo.model.Painting;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+import org.eclipse.rdf4j.spring.util.QueryResultUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * Class responsible for repository access for managing {@link Painting} entities.
+ *
+ * The class extends the {@link SimpleRDF4JCRUDDao}, providing capabilities for inserting and reading entities.
+ *
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@Component
+public class PaintingDao extends SimpleRDF4JCRUDDao {
+
+ public PaintingDao(RDF4JTemplate rdf4JTemplate) {
+ super(rdf4JTemplate);
+ }
+
+ @Override
+ protected void populateIdBindings(MutableBindings bindingsBuilder, IRI iri) {
+ bindingsBuilder.add(PAINTING_ID, iri);
+ }
+
+ @Override
+ protected RDF4JDao.NamedSparqlSupplierPreparer prepareNamedSparqlSuppliers(NamedSparqlSupplierPreparer preparer) {
+ return null;
+ }
+
+ @Override
+ protected Painting mapSolution(BindingSet querySolution) {
+ Painting painting = new Painting();
+ painting.setId(QueryResultUtils.getIRI(querySolution, PAINTING_ID));
+ painting.setTechnique(QueryResultUtils.getString(querySolution, PAINTING_TECHNIQUE));
+ painting.setTitle(QueryResultUtils.getString(querySolution, PAINTING_LABEL));
+ painting.setArtistId(QueryResultUtils.getIRI(querySolution, PAINTING_ARTIST_ID));
+ return painting;
+ }
+
+ @Override
+ protected String getReadQuery() {
+ return Queries.SELECT(PAINTING_ID, PAINTING_LABEL, PAINTING_TECHNIQUE, PAINTING_ARTIST_ID)
+ .where(
+ PAINTING_ID.isA(iri(EX.Painting))
+ .andHas(iri(EX.technique), PAINTING_TECHNIQUE)
+ .andHas(iri(RDFS.LABEL), PAINTING_LABEL),
+ PAINTING_ARTIST_ID.has(iri(EX.creatorOf), PAINTING_ID))
+ .getQueryString();
+ }
+
+ @Override
+ protected NamedSparqlSupplier getInsertSparql(Painting painting) {
+ return NamedSparqlSupplier.of("insert", () -> Queries.INSERT(
+ PAINTING_ID.isA(iri(EX.Painting))
+ .andHas(iri(EX.technique), PAINTING_TECHNIQUE)
+ .andHas(iri(RDFS.LABEL), PAINTING_LABEL),
+ PAINTING_ARTIST_ID.has(iri(EX.creatorOf), PAINTING_ID))
+ .getQueryString());
+ }
+
+ @Override
+ protected void populateBindingsForUpdate(MutableBindings bindingsBuilder, Painting painting) {
+ bindingsBuilder
+ .add(PAINTING_LABEL, painting.getTitle())
+ .add(PAINTING_TECHNIQUE, painting.getTechnique())
+ .add(PAINTING_ARTIST_ID, painting.getArtistId());
+ }
+
+ @Override
+ protected IRI getInputId(Painting painting) {
+ if (painting.getId() == null) {
+ return getRdf4JTemplate().getNewUUID();
+ }
+ return painting.getId();
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/Artist.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/Artist.java
new file mode 100644
index 00000000000..5946a71f998
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/Artist.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.model;
+
+import java.util.Objects;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder;
+import org.eclipse.rdf4j.sparqlbuilder.core.Variable;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class Artist {
+ public static final Variable ARTIST_ID = SparqlBuilder.var("artist_id");
+ public static final Variable ARTIST_FIRST_NAME = SparqlBuilder.var("artist_firstName");
+ public static final Variable ARTIST_LAST_NAME = SparqlBuilder.var("artist_lastName");
+ private IRI id;
+ private String firstName;
+ private String lastName;
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public IRI getId() {
+ return id;
+ }
+
+ public void setId(IRI id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Artist artist = (Artist) o;
+ return Objects.equals(id, artist.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/EX.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/EX.java
new file mode 100644
index 00000000000..2102af232f6
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/EX.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.model;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Namespace;
+import org.eclipse.rdf4j.model.impl.SimpleNamespace;
+import org.eclipse.rdf4j.model.util.Values;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class EX {
+ private static final Namespace base = new SimpleNamespace("ex", "http://example.org/");
+ public static final IRI Artist = Values.iri(base, "Artist");
+ public static final IRI Gallery = Values.iri(base, "Gallery");
+ public static final IRI Painting = Values.iri(base, "Painting");
+ public static final IRI Picasso = Values.iri(base, "Picasso");
+ public static final IRI VanGogh = Values.iri(base, "VanGogh");
+ public static final IRI Rembrandt = Values.iri(base, "Rembrandt");
+ public static final IRI street = Values.iri(base, "street");
+ public static final IRI city = Values.iri(base, "city");
+ public static final IRI country = Values.iri(base, "country");
+ public static final IRI creatorOf = Values.iri(base, "creatorOf");
+ public static final IRI technique = Values.iri(base, "technique");
+ public static final IRI starryNight = Values.iri(base, "starryNight");
+ public static final IRI sunflowers = Values.iri(base, "sunflowers");
+ public static final IRI potatoEaters = Values.iri(base, "potatoEaters");
+ public static final IRI guernica = Values.iri(base, "guernica");
+
+ public static IRI of(String localName) {
+ return Values.iri(base, localName);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/Painting.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/Painting.java
new file mode 100644
index 00000000000..a81efc79002
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/model/Painting.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.model;
+
+import java.util.Objects;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder;
+import org.eclipse.rdf4j.sparqlbuilder.core.Variable;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class Painting {
+ public static final Variable PAINTING_ID = SparqlBuilder.var("painting_id");
+ public static final Variable PAINTING_ARTIST_ID = SparqlBuilder.var("painting_artist_id");
+ public static final Variable PAINTING_TECHNIQUE = SparqlBuilder.var("painting_technique");
+ public static final Variable PAINTING_LABEL = SparqlBuilder.var("painting_label");
+
+ private IRI id;
+ private String title;
+ private String technique;
+ private IRI artistId;
+
+ public IRI getId() {
+ return id;
+ }
+
+ public void setId(IRI id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTechnique() {
+ return technique;
+ }
+
+ public void setTechnique(String technique) {
+ this.technique = technique;
+ }
+
+ public IRI getArtistId() {
+ return artistId;
+ }
+
+ public void setArtistId(IRI artistId) {
+ this.artistId = artistId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Painting painting = (Painting) o;
+ return Objects.equals(id, painting.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/service/ArtService.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/service/ArtService.java
new file mode 100644
index 00000000000..75972e6c2f3
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/service/ArtService.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.service;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toSet;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.spring.demo.dao.ArtistDao;
+import org.eclipse.rdf4j.spring.demo.dao.PaintingDao;
+import org.eclipse.rdf4j.spring.demo.model.Artist;
+import org.eclipse.rdf4j.spring.demo.model.Painting;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Uses {@link ArtistDao} and {@link PaintingDao} to query and manipulate the repository.
+ *
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@Component
+public class ArtService {
+ @Autowired
+ private ArtistDao artistDao;
+
+ @Autowired
+ private PaintingDao paintingDao;
+
+ @Transactional
+ public Artist createArtist(String firstName, String lastName) {
+ Artist artist = new Artist();
+ artist.setFirstName(firstName);
+ artist.setLastName(lastName);
+ return artistDao.save(artist);
+ }
+
+ @Transactional
+ public Painting createPainting(String title, String technique, IRI artist) {
+ Painting painting = new Painting();
+ painting.setTitle(title);
+ painting.setTechnique(technique);
+ painting.setArtistId(artist);
+ return paintingDao.save(painting);
+ }
+
+ @Transactional
+ public List getPaintings() {
+ return paintingDao.list();
+ }
+
+ @Transactional
+ public List getArtists() {
+ return artistDao.list();
+ }
+
+ @Transactional
+ public Set getArtistsWithoutPaintings() {
+ return artistDao.getArtistsWithoutPaintings();
+ }
+
+ @Transactional
+ public Map> getPaintingsGroupedByArtist() {
+ List paintings = paintingDao.list();
+ return paintings
+ .stream()
+ .collect(groupingBy(
+ p -> artistDao.getById(p.getArtistId()),
+ toSet()));
+ }
+
+ @Transactional
+ public IRI addArtist(Artist artist) {
+ return artistDao.saveAndReturnId(artist);
+ }
+
+ @Transactional
+ public IRI addPainting(Painting painting) {
+ return paintingDao.saveAndReturnId(painting);
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/support/InitialDataInserter.java b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/support/InitialDataInserter.java
new file mode 100644
index 00000000000..6fe04205eb1
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/java/org/eclipse/rdf4j/spring/demo/support/InitialDataInserter.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.support;
+
+import org.eclipse.rdf4j.spring.support.DataInserter;
+import org.springframework.core.io.Resource;
+
+import jakarta.annotation.PostConstruct;
+
+/**
+ * Inserts data from the specified TTL file into the repository at startup.
+ *
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class InitialDataInserter {
+ DataInserter dataInserter;
+ Resource ttlFile;
+
+ public InitialDataInserter(DataInserter dataInserter, Resource ttlFile) {
+ this.dataInserter = dataInserter;
+ this.ttlFile = ttlFile;
+ }
+
+ @PostConstruct
+ public void insertDemoData() {
+ this.dataInserter.insertData(ttlFile);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/resources/application.properties b/spring6-components/rdf4j-spring6-demo/src/main/resources/application.properties
new file mode 100644
index 00000000000..fbaab9a326b
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+rdf4j.spring.repository.inmemory.enabled=true
+rdf4j.spring.pool.enabled=true
+rdf4j.spring.operationcache.enabled=false
+rdf4j.spring.operationlog.enabled=false
+rdf4j.spring.resultcache.enabled=false
+rdf4j.spring.tx.enabled=true
\ No newline at end of file
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/resources/artists.ttl b/spring6-components/rdf4j-spring6-demo/src/main/resources/artists.ttl
new file mode 100644
index 00000000000..041b4f9597c
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/resources/artists.ttl
@@ -0,0 +1,38 @@
+@prefix ex: .
+@prefix foaf: .
+@prefix rdfs: .
+
+ex:Picasso a ex:Artist ;
+ foaf:firstName "Pablo" ;
+ foaf:surname "Picasso";
+ ex:creatorOf ex:guernica ;
+ ex:homeAddress _:node1 .
+
+_:node1 ex:street "31 Art Gallery" ;
+ ex:city "Madrid" ;
+ ex:country "Spain" .
+
+ex:guernica a ex:Painting ;
+ rdfs:label "Guernica";
+ ex:technique "oil on canvas".
+
+ex:VanGogh a ex:Artist ;
+ foaf:firstName "Vincent" ;
+ foaf:surname "van Gogh";
+ ex:creatorOf ex:starryNight, ex:sunflowers, ex:potatoEaters .
+
+ex:starryNight a ex:Painting ;
+ ex:technique "oil on canvas";
+ rdfs:label "Starry Night" .
+
+ex:sunflowers a ex:Painting ;
+ ex:technique "oil on canvas";
+ rdfs:label "Sunflowers" .
+
+ex:potatoEaters a ex:Painting ;
+ ex:technique "oil on canvas";
+ rdfs:label "The Potato Eaters" .
+
+ex:Rembrandt a ex:Artist ;
+ foaf:firstName "Rembrandt Harmensz" ;
+ foaf:surname "van Rijn".
\ No newline at end of file
diff --git a/spring6-components/rdf4j-spring6-demo/src/main/resources/logback.xml b/spring6-components/rdf4j-spring6-demo/src/main/resources/logback.xml
new file mode 100644
index 00000000000..cc3c5b4b7b3
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/main/resources/logback.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
diff --git a/spring6-components/rdf4j-spring6-demo/src/test/java/org/eclipse/rdf4j/spring/demo/TestConfig.java b/spring6-components/rdf4j-spring6-demo/src/test/java/org/eclipse/rdf4j/spring/demo/TestConfig.java
new file mode 100644
index 00000000000..4c857e2a395
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/test/java/org/eclipse/rdf4j/spring/demo/TestConfig.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo;
+
+import org.eclipse.rdf4j.spring.support.DataInserter;
+import org.eclipse.rdf4j.spring.test.RDF4JTestConfig;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@TestConfiguration
+@EnableTransactionManagement
+@Import(RDF4JTestConfig.class)
+@ComponentScan("org.eclipse.rdf4j.spring.demo.*")
+public class TestConfig {
+
+ @Bean
+ DataInserter getDataInserter() {
+ return new DataInserter();
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/test/java/org/eclipse/rdf4j/spring/demo/dao/ArtistDaoTests.java b/spring6-components/rdf4j-spring6-demo/src/test/java/org/eclipse/rdf4j/spring/demo/dao/ArtistDaoTests.java
new file mode 100644
index 00000000000..0018facbcfb
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/test/java/org/eclipse/rdf4j/spring/demo/dao/ArtistDaoTests.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.demo.dao;
+
+import java.util.Set;
+
+import org.eclipse.rdf4j.spring.demo.TestConfig;
+import org.eclipse.rdf4j.spring.demo.model.Artist;
+import org.eclipse.rdf4j.spring.demo.model.EX;
+import org.eclipse.rdf4j.spring.support.DataInserter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@SpringJUnitConfig(classes = { TestConfig.class })
+@TestPropertySource("classpath:application.properties")
+@TestPropertySource(
+ properties = {
+ "rdf4j.spring.repository.inmemory.enabled=true",
+ "rdf4j.spring.repository.inmemory.use-shacl-sail=true",
+ "rdf4j.spring.tx.enabled=true",
+ "rdf4j.spring.resultcache.enabled=false",
+ "rdf4j.spring.operationcache.enabled=false",
+ "rdf4j.spring.pool.enabled=true",
+ "rdf4j.spring.pool.max-connections=2"
+ })
+@DirtiesContext
+public class ArtistDaoTests {
+
+ @Autowired
+ private ArtistDao artistDao;
+
+ @BeforeAll
+ public static void insertTestData(
+ @Autowired DataInserter dataInserter,
+ @Value("classpath:artists.ttl") Resource dataFile) {
+ dataInserter.insertData(dataFile);
+ }
+
+ @Test
+ public void testReadArtist() {
+ Artist a = artistDao.getById(EX.Picasso);
+ Assertions.assertEquals("Picasso", a.getLastName());
+ Assertions.assertEquals("Pablo", a.getFirstName());
+ }
+
+ @Test
+ public void testWriteArtist() {
+ Artist a = new Artist();
+ a.setFirstName("Salvador");
+ a.setLastName("DalĂ");
+ Artist savedDali = artistDao.save(a);
+ Assertions.assertNotNull(savedDali.getId());
+ Artist reloadedDali = artistDao.getById(savedDali.getId());
+ Assertions.assertEquals(savedDali, reloadedDali);
+ }
+
+ @Test
+ public void testReadArtistWithoutPaintings() {
+ Set withoutPaintings = artistDao.getArtistsWithoutPaintings();
+ Assertions.assertEquals(1, withoutPaintings.size());
+ Artist a = artistDao.getById(EX.Rembrandt);
+ Assertions.assertTrue(withoutPaintings.contains(a));
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6-demo/src/test/resources/logback.xml b/spring6-components/rdf4j-spring6-demo/src/test/resources/logback.xml
new file mode 100644
index 00000000000..2c07d55c40c
--- /dev/null
+++ b/spring6-components/rdf4j-spring6-demo/src/test/resources/logback.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
diff --git a/spring6-components/rdf4j-spring6/pom.xml b/spring6-components/rdf4j-spring6/pom.xml
new file mode 100644
index 00000000000..34d17e19235
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/pom.xml
@@ -0,0 +1,89 @@
+
+
+ 4.0.0
+
+ org.eclipse.rdf4j
+ rdf4j-spring6-components
+ 5.3.0-SNAPSHOT
+
+ rdf4j-spring6
+ RDF4J: Spring6
+ Spring6 integration for RDF4J
+ jar
+
+
+
+ org.eclipse.rdf4j
+ rdf4j-runtime
+ ${project.version}
+ pom
+
+
+ org.eclipse.rdf4j
+ rdf4j-sparqlbuilder
+ ${project.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework
+ spring-jcl
+
+
+
+
+ com.google.guava
+ guava
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework
+ spring-tx
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+ org.apache.commons
+ commons-pool2
+ 2.8.1
+
+
+ org.mock-server
+ mockserver-junit-jupiter-no-dependencies
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/RDF4JConfig.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/RDF4JConfig.java
new file mode 100644
index 00000000000..e8e73500fa0
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/RDF4JConfig.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring;
+
+import java.lang.invoke.MethodHandles;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.eclipse.rdf4j.common.annotation.Experimental;
+import org.eclipse.rdf4j.repository.Repository;
+import org.eclipse.rdf4j.repository.RepositoryConnection;
+import org.eclipse.rdf4j.spring.operationcache.CachingOperationInstantiator;
+import org.eclipse.rdf4j.spring.operationcache.OperationCacheProperties;
+import org.eclipse.rdf4j.spring.operationlog.LoggingRepositoryConnectionFactory;
+import org.eclipse.rdf4j.spring.operationlog.log.OperationLog;
+import org.eclipse.rdf4j.spring.pool.PoolProperties;
+import org.eclipse.rdf4j.spring.pool.PooledRepositoryConnectionFactory;
+import org.eclipse.rdf4j.spring.resultcache.CachingRepositoryConnectionFactory;
+import org.eclipse.rdf4j.spring.resultcache.ResultCacheProperties;
+import org.eclipse.rdf4j.spring.support.DirectOperationInstantiator;
+import org.eclipse.rdf4j.spring.support.OperationInstantiator;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+import org.eclipse.rdf4j.spring.support.UUIDSource;
+import org.eclipse.rdf4j.spring.support.connectionfactory.DirectRepositoryConnectionFactory;
+import org.eclipse.rdf4j.spring.support.connectionfactory.RepositoryConnectionFactory;
+import org.eclipse.rdf4j.spring.tx.TransactionalRepositoryConnectionFactory;
+import org.eclipse.rdf4j.spring.tx.TxProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@Experimental
+@Configuration
+@EnableTransactionManagement
+public class RDF4JConfig {
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Bean
+ RDF4JTemplate getRdf4JTemplate(@Autowired RepositoryConnectionFactory repositoryConnectionFactory,
+ @Autowired(required = false) OperationCacheProperties operationCacheProperties,
+ @Autowired ResourceLoader resourceLoader,
+ @Autowired(required = false) UUIDSource uuidSource) {
+ OperationInstantiator operationInstantiator;
+ if (operationCacheProperties != null && operationCacheProperties.isEnabled()) {
+ logger.debug("Operation caching is enabled");
+ operationInstantiator = new CachingOperationInstantiator();
+ } else {
+ logger.debug("Operation caching is not enabled");
+ operationInstantiator = new DirectOperationInstantiator();
+ }
+ return new RDF4JTemplate(repositoryConnectionFactory, operationInstantiator, resourceLoader, uuidSource);
+ }
+
+ @Bean
+ RepositoryConnectionFactory getRepositoryConnectionFactory(
+ @Autowired Repository repository,
+ @Autowired(required = false) PoolProperties poolProperties,
+ @Autowired(required = false) ResultCacheProperties resultCacheProperties,
+ @Autowired(required = false) OperationLog operationLog,
+ @Autowired(required = false) TxProperties txProperties) {
+ RepositoryConnectionFactory factory = getDirectRepositoryConnectionFactory(repository);
+
+ if (poolProperties != null && poolProperties.isEnabled()) {
+ logger.debug("Connection pooling is enabled");
+ factory = wrapWithPooledRepositoryConnectionFactory(factory, poolProperties);
+ } else {
+ logger.debug("Connection pooling is not enabled");
+ }
+ if (resultCacheProperties != null && resultCacheProperties.isEnabled()) {
+ factory = wrapWithCachingRepositoryConnectionFactory(factory, resultCacheProperties);
+ logger.debug("Result caching is enabled");
+ } else {
+ logger.debug("Result caching is not enabled");
+ }
+ if (operationLog != null) {
+ factory = wrapWithLoggingRepositoryConnectionFactory(factory, operationLog);
+ logger.debug("Query logging is enabled");
+ } else {
+ logger.debug("Query logging is not enabled");
+ }
+ if (txProperties != null && txProperties.isEnabled()) {
+ factory = wrapWithTxRepositoryConnectionFactory(factory);
+ logger.debug("Spring transaction integration is enabled");
+ } else {
+ logger.debug("Spring transaction integration is not enabled");
+ }
+ return factory;
+ }
+
+ RepositoryConnectionFactory getDirectRepositoryConnectionFactory(Repository repository) {
+ return new DirectRepositoryConnectionFactory(repository);
+ }
+
+ RepositoryConnectionFactory wrapWithPooledRepositoryConnectionFactory(
+ RepositoryConnectionFactory delegate, PoolProperties poolProperties) {
+ GenericObjectPoolConfig config = new GenericObjectPoolConfig<>();
+ config.setMaxTotal(poolProperties.getMaxConnections());
+ config.setMinIdle(poolProperties.getMinIdleConnections());
+ config.setTimeBetweenEvictionRunsMillis(
+ poolProperties.getTimeBetweenEvictionRuns().toMillis());
+ config.setTestWhileIdle(poolProperties.isTestWhileIdle());
+ return new PooledRepositoryConnectionFactory(delegate, config);
+ }
+
+ RepositoryConnectionFactory wrapWithLoggingRepositoryConnectionFactory(
+ RepositoryConnectionFactory delegate, OperationLog operationLog) {
+ return new LoggingRepositoryConnectionFactory(delegate, operationLog);
+ }
+
+ RepositoryConnectionFactory wrapWithCachingRepositoryConnectionFactory(
+ RepositoryConnectionFactory delegate, ResultCacheProperties resultCacheProperties) {
+ return new CachingRepositoryConnectionFactory(delegate, resultCacheProperties);
+ }
+
+ TransactionalRepositoryConnectionFactory wrapWithTxRepositoryConnectionFactory(
+ RepositoryConnectionFactory delegate) {
+ return new TransactionalRepositoryConnectionFactory(delegate);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/RDF4JCRUDDao.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/RDF4JCRUDDao.java
new file mode 100644
index 00000000000..9848444b831
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/RDF4JCRUDDao.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.query.BindingSet;
+import org.eclipse.rdf4j.spring.dao.exception.IncorrectResultSetSizeException;
+import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.BindingsBuilder;
+import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.MutableBindings;
+import org.eclipse.rdf4j.spring.dao.support.key.CompositeKey;
+import org.eclipse.rdf4j.spring.dao.support.opbuilder.TupleQueryEvaluationBuilder;
+import org.eclipse.rdf4j.spring.dao.support.opbuilder.UpdateExecutionBuilder;
+import org.eclipse.rdf4j.spring.dao.support.sparql.NamedSparqlSupplier;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+
+/**
+ * Base class for DAOs providing CRUD functionality. The class allows for entities to be represented with different
+ * classes for read (ENTITY type) vs write (INPUT type) operations. DAOs that do not require this distinction must use
+ * the same class for both parameters.
+ *
+ * @param
+ * @param
+ * @param
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public abstract class RDF4JCRUDDao extends RDF4JDao {
+ private static final String KEY_READ_QUERY = "readQuery";
+ public static final String KEY_PREFIX_INSERT = "insert";
+ public static final String KEY_PREFIX_UPDATE = "update";
+ private final Class idClass;
+
+ /**
+ * Constructor that provides the type of the ID to the base implementation. This constructor has to be used if the
+ * ID is anything but IRI.
+ */
+ public RDF4JCRUDDao(RDF4JTemplate rdf4JTemplate, Class idClass) {
+ super(rdf4JTemplate);
+ this.idClass = idClass;
+ }
+
+ /**
+ * Constructor to be used by implementations that use IRI for the ID type.
+ */
+ public RDF4JCRUDDao(RDF4JTemplate rdf4JTemplate) {
+ this(rdf4JTemplate, (Class) IRI.class);
+ }
+
+ /**
+ * Saves the entity, loads it again and returns it. If the modified entity is not required, clients should prefer
+ * {@link #saveAndReturnId(Object, Object)} or {@link #saveAndReturnId(Object)}
+ */
+ public final ENTITY save(INPUT input) {
+ ID id = getInputId(input);
+ final ID finalId = saveAndReturnId(input, id);
+ return getById(finalId);
+ }
+
+ public ID saveAndReturnId(INPUT input) {
+ return saveAndReturnId(input, getInputId(input));
+ }
+
+ /**
+ * Saves the entity and returns its (possibly newly generated) ID.
+ *
+ * @param input the entity
+ * @param id the id or null for a new entity.
+ * @return the id (a newly generated one if the specified id is null, otherwise just id.
+ */
+ public ID saveAndReturnId(INPUT input, ID id) {
+ if (id != null) {
+ // delete triples for the modify case
+ deleteForUpdate(id);
+ }
+ final ID finalId = getOrGenerateId(id);
+ getInsertQueryOrUseCached(input)
+ .withBindings(bindingsBuilder -> populateIdBindings(bindingsBuilder, finalId))
+ .withBindings(bindingsBuilder -> populateBindingsForUpdate(bindingsBuilder, input))
+ .execute(bindings -> postProcessUpdate(input, bindings));
+ return finalId;
+ }
+
+ /**
+ * When updating an entity via {@link #save(Object)}, its triples are removed first using this method. The default
+ * implementation used {@link RDF4JTemplate#deleteTriplesWithSubject(IRI)}. If more complex deletion behaviour (e.g.
+ * cascading) is needed, this method should be overriden.
+ */
+ protected void deleteForUpdate(ID id) {
+ IRI iri = convertIdToIri(id);
+ getRdf4JTemplate().deleteTriplesWithSubject(iri);
+ }
+
+ private ID getOrGenerateId(ID id) {
+ boolean idPresent;
+ if (id instanceof CompositeKey) {
+ idPresent = ((CompositeKey) id).isPresent();
+ } else {
+ idPresent = id != null;
+ }
+ if (!idPresent) {
+ id = generateNewId(id);
+ }
+ return id;
+ }
+
+ /**
+ * Converts the provided id to an IRI. The default implementation only works for DAOs that use IRI ids.
+ *
+ * @param id
+ * @return
+ */
+ protected IRI convertIdToIri(ID id) {
+ if (id == null) {
+ return null;
+ }
+ if (idClass.equals(IRI.class)) {
+ return (IRI) id;
+ }
+ throw new UnsupportedOperationException(
+ "Cannot generically convert IDs to IRIs. The subclass must implement convertToIri(ID)");
+ }
+
+ /**
+ * Generates a new id for an entity. The default implementation only works for IRI ids.
+ *
+ * @param providedId
+ * @return a new id.
+ */
+ protected ID generateNewId(ID providedId) {
+ if (idClass.equals(IRI.class)) {
+ return (ID) getRdf4JTemplate().getNewUUID();
+ }
+ throw new UnsupportedOperationException(
+ "Cannot generically generate any other IDs than IRIs. The subclass must implement generateNewId(ID)");
+ }
+
+ private UpdateExecutionBuilder getInsertQueryOrUseCached(INPUT input) {
+ final NamedSparqlSupplier cs = getInsertSparql(input);
+ String key = KEY_PREFIX_INSERT + cs.getName();
+ return getRdf4JTemplate().update(this.getClass(), key, cs.getSparqlSupplier());
+ }
+
+ public final List list() {
+ return getReadQueryOrUseCached()
+ .evaluateAndConvert()
+ .toList(this::mapSolution, this::postProcessMappedSolution);
+ }
+
+ private TupleQueryEvaluationBuilder getReadQueryOrUseCached() {
+ return getRdf4JTemplate().tupleQuery(getClass(), KEY_READ_QUERY, this::getReadQuery);
+ }
+
+ /**
+ * Obtains the entity with the specified id, throwing an exception if none is found.
+ *
+ * @param id the id
+ * @return the entity
+ * @throws IncorrectResultSetSizeException if no entity is found with the specified id
+ */
+ public final ENTITY getById(ID id) {
+ return getByIdOptional(id)
+ .orElseThrow(
+ () -> new IncorrectResultSetSizeException(
+ "Expected to find exactly one entity but found 0", 1, 0));
+ }
+
+ /**
+ * Obtains an optional entity with the specified id.
+ *
+ * @param id the id
+ * @return an Optional maybe containing the entity
+ */
+ public final Optional getByIdOptional(ID id) {
+ return getReadQueryOrUseCached()
+ .withBindings(bindingsBuilder -> populateIdBindings(bindingsBuilder, id))
+ .evaluateAndConvert()
+ .toSingletonOptional(this::mapSolution, this::postProcessMappedSolution);
+ }
+
+ /**
+ * Naive implementation using {@link RDF4JTemplate#delete(IRI)}. DAOs that need more complex deletion behaviour
+ * (e.g. cascading) should override this method.
+ */
+ public void delete(ID id) {
+ if (idClass.equals(IRI.class)) {
+ getRdf4JTemplate().delete((IRI) id);
+ } else {
+ throw new UnsupportedOperationException(
+ "Cannot generically delete instances that do not use IRI ids. The subclass must implement delete(ID)");
+ }
+ }
+
+ /**
+ * Returns the SPARQL string used to read an instance of T from the database. The base implementation will cache the
+ * query string, so implementations should not try to cache the query.
+ */
+ protected String getReadQuery() {
+ throw new UnsupportedOperationException(
+ "Cannot perform generic read operation: subclass does not override getReadQuery()");
+ }
+
+ /**
+ * Map one solution of the readQuery to the type of this DAO.
+ */
+ protected ENTITY mapSolution(BindingSet querySolution) {
+ throw new UnsupportedOperationException(
+ "Cannot perform generic read operation: subclass does not override mapSolution()");
+ }
+
+ /**
+ * Callback invoked after mapping a solution to an entity, allowing subclasses to modify the entity before returning
+ * it to the client.
+ */
+ protected ENTITY postProcessMappedSolution(ENTITY entity) {
+ return entity;
+ }
+
+ /**
+ * Callback invoked after a successful insert/update.
+ */
+ protected void postProcessUpdate(INPUT input, Map bindings) {
+ // empty default implementation
+ }
+
+ /**
+ * Returns the SPARQL string used to write an instance of T to the database. The instance to be inserted is passed
+ * to the function so implementations can decide which query to use based on the instance.
+ */
+ protected NamedSparqlSupplier getInsertSparql(INPUT input) {
+ throw new UnsupportedOperationException(
+ "Cannot perform generic write operation: subclass does not override getInsertQuery()");
+ }
+
+ /**
+ * Returns the SPARQL string used to update an instance of T in the database. The instance to be updated is passed
+ * to the function so implementations can decide which query to use based on the instance.
+ */
+ protected NamedSparqlSupplier getUpdateSparql(INPUT input) {
+ throw new UnsupportedOperationException(
+ "Cannot perform generic write operation: subclass does not override getUpdateQuery()");
+ }
+
+ /**
+ * Binds the instance id to query variable(s).
+ */
+ protected abstract void populateIdBindings(MutableBindings bindingsBuilder, ID id);
+
+ /**
+ * Sets the non-id bindings on for the write query such that the instance of type I is written to the database. ID
+ * bindings are set through populateIdBindings()
+ */
+ protected void populateBindingsForUpdate(MutableBindings bindingsBuilder, INPUT input) {
+ throw new UnsupportedOperationException(
+ "Cannot perform generic write operation: subclass does not override populateBindingsForUpdate()");
+ }
+
+ /**
+ * Obtains the id of the input instance or null if it is new (or a partially populated composite key).
+ */
+ protected ID getInputId(INPUT input) {
+ throw new UnsupportedOperationException(
+ "Cannot perform generic write operation: subclass does not override getInputId()");
+ }
+
+ /**
+ * Returns a new BindingsBuilder for your convenience.
+ */
+ protected static BindingsBuilder newBindingsBuilder() {
+ return new BindingsBuilder();
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/RDF4JDao.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/RDF4JDao.java
new file mode 100644
index 00000000000..4bec05d581c
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/RDF4JDao.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.rdf4j.common.annotation.Experimental;
+import org.eclipse.rdf4j.spring.dao.exception.RDF4JDaoException;
+import org.eclipse.rdf4j.spring.dao.support.opbuilder.GraphQueryEvaluationBuilder;
+import org.eclipse.rdf4j.spring.dao.support.opbuilder.TupleQueryEvaluationBuilder;
+import org.eclipse.rdf4j.spring.dao.support.opbuilder.UpdateExecutionBuilder;
+import org.eclipse.rdf4j.spring.dao.support.sparql.NamedSparqlSupplier;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+@Experimental
+public abstract class RDF4JDao {
+ private final RDF4JTemplate rdf4JTemplate;
+
+ private final Map namedSparqlSuppliers = new ConcurrentHashMap<>();
+
+ public RDF4JDao(RDF4JTemplate rdf4JTemplate) {
+ this.rdf4JTemplate = rdf4JTemplate;
+ prepareNamedSparqlSuppliers(new NamedSparqlSupplierPreparer());
+ }
+
+ protected RDF4JTemplate getRdf4JTemplate() {
+ return rdf4JTemplate;
+ }
+
+ protected abstract NamedSparqlSupplierPreparer prepareNamedSparqlSuppliers(
+ NamedSparqlSupplierPreparer preparer);
+
+ /**
+ * Prepares the specified SPARQL string for later use, e.g. in
+ * {@link RDF4JTemplate#tupleQuery(Class, NamedSparqlSupplier)}.
+ */
+ private void prepareNamedSparqlSupplier(String key, String sparql) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(sparql);
+ namedSparqlSuppliers.put(key, new NamedSparqlSupplier(key, () -> sparql));
+ }
+
+ /**
+ * Reads the SPARQL string from the specified resource using a {@link org.springframework.core.io.ResourceLoader}
+ * and prepares it for later use, e.g. in {@link RDF4JTemplate#tupleQuery(Class, NamedSparqlSupplier)}.
+ */
+ private void prepareNamedSparqlSupplierFromResource(String key, String resourceName) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(resourceName);
+ String sparqlString = getRdf4JTemplate().getStringSupplierFromResourceContent(resourceName).get();
+ namedSparqlSuppliers.put(key, new NamedSparqlSupplier(key, () -> sparqlString));
+ }
+
+ /**
+ * Obtains the {@link NamedSparqlSupplier} with the specified key for use in, e.g.,
+ * {@link RDF4JTemplate#tupleQuery(Class, NamedSparqlSupplier)}.
+ */
+ protected NamedSparqlSupplier getNamedSparqlSupplier(String key) {
+ Objects.requireNonNull(key);
+ NamedSparqlSupplier supplier = namedSparqlSuppliers.get(key);
+ if (supplier == null) {
+ throw new RDF4JDaoException(
+ String.format(
+ "No NamedSparqlOperation found for key %s. Prepare it using Rdf4JDao.prepareNamedSparqlSuppliers() before calling this method!",
+ key));
+ }
+ return supplier;
+ }
+
+ protected String getNamedSparqlString(String key) {
+ return getNamedSparqlSupplier(key).getSparqlSupplier().get();
+ }
+
+ protected TupleQueryEvaluationBuilder getNamedTupleQuery(String key) {
+ return getRdf4JTemplate().tupleQuery(getClass(), getNamedSparqlSupplier(key));
+ }
+
+ protected GraphQueryEvaluationBuilder getNamedGraphQuery(String key) {
+ return getRdf4JTemplate().graphQuery(getClass(), getNamedSparqlSupplier(key));
+ }
+
+ protected UpdateExecutionBuilder getNamedUpdate(String key) {
+ return getRdf4JTemplate().update(getClass(), getNamedSparqlSupplier(key));
+ }
+
+ public class NamedSparqlSupplierPreparer {
+
+ private NamedSparqlSupplierPreparer() {
+ }
+
+ /**
+ * For the specified key, {@link java.util.function.Supplier} is registered with the
+ * subsequent supplySparql* method.
+ */
+ public NamedSparqlSupplierFinishBuilder forKey(String key) {
+ return new NamedSparqlSupplierFinishBuilder(key);
+ }
+ }
+
+ public class NamedSparqlSupplierFinishBuilder {
+ private final String key;
+
+ public NamedSparqlSupplierFinishBuilder(String key) {
+ this.key = key;
+ }
+
+ /**
+ * Supplies the specified SPARQL String.
+ */
+ public NamedSparqlSupplierPreparer supplySparql(String sparql) {
+ prepareNamedSparqlSupplier(key, sparql);
+ return new NamedSparqlSupplierPreparer();
+ }
+
+ /**
+ * Loads the specified resource using a {@link org.springframework.core.io.ResourceLoader} and
+ * supplies its content as String, the assumption is that it contains a SPARQL operation.
+ */
+ public NamedSparqlSupplierPreparer supplySparqlFromResource(String resource) {
+ prepareNamedSparqlSupplierFromResource(key, resource);
+ return new NamedSparqlSupplierPreparer();
+ }
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/SimpleRDF4JCRUDDao.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/SimpleRDF4JCRUDDao.java
new file mode 100644
index 00000000000..d454f047588
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/SimpleRDF4JCRUDDao.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao;
+
+import org.eclipse.rdf4j.common.annotation.Experimental;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+
+@Experimental
+public abstract class SimpleRDF4JCRUDDao extends RDF4JCRUDDao {
+ public SimpleRDF4JCRUDDao(RDF4JTemplate rdf4JTemplate, Class idClass) {
+ super(rdf4JTemplate, idClass);
+ }
+
+ public SimpleRDF4JCRUDDao(RDF4JTemplate rdf4JTemplate) {
+ super(rdf4JTemplate);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/IncorrectResultSetSizeException.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/IncorrectResultSetSizeException.java
new file mode 100644
index 00000000000..dbacfc618ec
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/IncorrectResultSetSizeException.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.exception;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class IncorrectResultSetSizeException extends RDF4JDaoException {
+ int expectedSize;
+ int actualSize;
+
+ public IncorrectResultSetSizeException(int expectedSize, int actualSize) {
+ super(makeMessage(expectedSize, actualSize));
+ this.expectedSize = expectedSize;
+ this.actualSize = actualSize;
+ }
+
+ private static String makeMessage(int expectedSize, int actualSize) {
+ return String.format("Expected %d results but got %d", expectedSize, actualSize);
+ }
+
+ public IncorrectResultSetSizeException(String message, int expectedSize, int actualSize) {
+ super(message);
+ this.expectedSize = expectedSize;
+ this.actualSize = actualSize;
+ }
+
+ public IncorrectResultSetSizeException(
+ String message, Throwable cause, int expectedSize, int actualSize) {
+ super(message, cause);
+ this.expectedSize = expectedSize;
+ this.actualSize = actualSize;
+ }
+
+ public IncorrectResultSetSizeException(Throwable cause, int expectedSize, int actualSize) {
+ super(makeMessage(expectedSize, actualSize), cause);
+ this.expectedSize = expectedSize;
+ this.actualSize = actualSize;
+ }
+
+ public int getExpectedSize() {
+ return expectedSize;
+ }
+
+ public int getActualSize() {
+ return actualSize;
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/RDF4JDaoException.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/RDF4JDaoException.java
new file mode 100644
index 00000000000..00258527e12
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/RDF4JDaoException.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.exception;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class RDF4JDaoException extends RDF4JSpringException {
+ public RDF4JDaoException() {
+ }
+
+ public RDF4JDaoException(String message) {
+ super(message);
+ }
+
+ public RDF4JDaoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RDF4JDaoException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/RDF4JSpringException.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/RDF4JSpringException.java
new file mode 100644
index 00000000000..1ff01905cc9
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/RDF4JSpringException.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.exception;
+
+import org.eclipse.rdf4j.common.exception.RDF4JException;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class RDF4JSpringException extends RDF4JException {
+ public RDF4JSpringException() {
+ }
+
+ public RDF4JSpringException(String message) {
+ super(message);
+ }
+
+ public RDF4JSpringException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RDF4JSpringException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/UnexpectedResultException.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/UnexpectedResultException.java
new file mode 100644
index 00000000000..c9ef0a9aed4
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/UnexpectedResultException.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.exception;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class UnexpectedResultException extends RDF4JDaoException {
+ public UnexpectedResultException() {
+ }
+
+ public UnexpectedResultException(String message) {
+ super(message);
+ }
+
+ public UnexpectedResultException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnexpectedResultException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/UnsupportedDataTypeException.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/UnsupportedDataTypeException.java
new file mode 100644
index 00000000000..08563fa72e4
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/UnsupportedDataTypeException.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.exception;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class UnsupportedDataTypeException extends UnexpectedResultException {
+ public UnsupportedDataTypeException() {
+ }
+
+ public UnsupportedDataTypeException(String message) {
+ super(message);
+ }
+
+ public UnsupportedDataTypeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnsupportedDataTypeException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/mapper/ExceptionMapper.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/mapper/ExceptionMapper.java
new file mode 100644
index 00000000000..b361f2336a5
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/exception/mapper/ExceptionMapper.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.exception.mapper;
+
+import org.eclipse.rdf4j.spring.dao.exception.RDF4JSpringException;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class ExceptionMapper {
+
+ public static RDF4JSpringException mapException(String message, Exception e) {
+ if (e instanceof RDF4JSpringException) {
+ return (RDF4JSpringException) e;
+ }
+ return new RDF4JSpringException(message, e);
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/package-info.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/package-info.java
new file mode 100644
index 00000000000..00317c279c0
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/package-info.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+/**
+ *
+ *
+ *
Rdf4j-Spring DAO
+ *
+ * Support for custom DAO (data access object) implementations.
+ *
+ *
+ * Such custom DAO implementations get access to the following subsystems:
+ *
+ *
+ *
{@link org.eclipse.rdf4j.spring.support.RDF4JTemplate Rdf4JTemplate}: Central service for accessing
+ * repositories,executing queries and updates, as well as transforming results into java entities or collections
+ *
{@link org.eclipse.rdf4j.spring.dao.support.sparql.NamedSparqlSupplier NamedSparqlSupplier}: DAO-specific map of
+ * SPARQL Strings aiding efficient generation and caching of operaitons
+ *
+ *
+ *
+ * There are two variants of DAOs:
+ *
+ *
+ *
{@link org.eclipse.rdf4j.spring.dao.RDF4JDao Rdf4JDao}: Base class for DAOs with support for named operations and
+ * access
+ *
{@link org.eclipse.rdf4j.spring.dao.RDF4JCRUDDao Rdf4JCRUDDao}: Base class for DAOs that are associated with
+ * specific entity classes, providing additional support for CRUD operations on these entities.
+ *
+ *
+ * @since 4.0.0
+ * @author Florian Kleedorfer
+ *
+ */
+package org.eclipse.rdf4j.spring.dao;
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/BindingSetMapper.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/BindingSetMapper.java
new file mode 100644
index 00000000000..f8805cbf472
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/BindingSetMapper.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.support;
+
+import java.util.function.Function;
+
+import org.eclipse.rdf4j.query.BindingSet;
+
+/**
+ * Maps a query solution to an instance.
+ *
+ * @param
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public interface BindingSetMapper extends Function {
+
+ /**
+ * Maps a query solution to an instance of T. If the return value is null the mapper
+ * indicates that the solution is to be disregarded.
+ *
+ * @return an instance of T or null if the solution should be ignored.
+ */
+ @Override
+ T apply(BindingSet bindings);
+
+ static BindingSetMapper identity() {
+ return b -> b;
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/MappingPostProcessor.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/MappingPostProcessor.java
new file mode 100644
index 00000000000..65548c7eabb
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/MappingPostProcessor.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.support;
+
+import java.util.function.Function;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public interface MappingPostProcessor extends Function {
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/RelationMapBuilder.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/RelationMapBuilder.java
new file mode 100644
index 00000000000..d4b982fbda8
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/RelationMapBuilder.java
@@ -0,0 +1,320 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.support;
+
+import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri;
+import static org.eclipse.rdf4j.spring.util.QueryResultUtils.getIRI;
+import static org.eclipse.rdf4j.spring.util.QueryResultUtils.getIRIOptional;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.Value;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.query.BindingSet;
+import org.eclipse.rdf4j.sparqlbuilder.core.Projectable;
+import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder;
+import org.eclipse.rdf4j.sparqlbuilder.core.Variable;
+import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries;
+import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern;
+import org.eclipse.rdf4j.sparqlbuilder.graphpattern.TriplePattern;
+import org.eclipse.rdf4j.sparqlbuilder.rdf.RdfPredicate;
+import org.eclipse.rdf4j.spring.dao.support.bindingsBuilder.BindingsBuilder;
+import org.eclipse.rdf4j.spring.dao.support.opbuilder.TupleQueryEvaluationBuilder;
+import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public class RelationMapBuilder {
+ public static final Variable _relSubject = SparqlBuilder.var("rel_subject");
+ public static final Variable _relObject = SparqlBuilder.var("rel_object");
+ private static final Variable _relKey = SparqlBuilder.var("rel_key");
+ private static final Variable _relValue = SparqlBuilder.var("rel_value");
+ private static final IRI NOTHING = SimpleValueFactory.getInstance()
+ .createIRI("urn:java:relationDaoSupport:Nothing");
+ private final RdfPredicate predicate;
+ private GraphPattern[] constraints = new GraphPattern[0];
+ private final RDF4JTemplate rdf4JTemplate;
+ private boolean isRelationOptional = false;
+ private boolean isSubjectKeyed = true;
+ private final BindingsBuilder bindingsBuilder = new BindingsBuilder();
+
+ public RelationMapBuilder(RDF4JTemplate rdf4JTemplate, RdfPredicate predicate) {
+ this.rdf4JTemplate = rdf4JTemplate;
+ this.predicate = predicate;
+ }
+
+ public RelationMapBuilder(RDF4JTemplate rdf4JTemplate, IRI predicate) {
+ this.rdf4JTemplate = rdf4JTemplate;
+ this.predicate = iri(predicate);
+ }
+
+ /**
+ * Constrains the result iff the {@link GraphPattern} contains the variables {@link RelationMapBuilder#_relSubject}
+ * and/or {@link RelationMapBuilder#_relObject}, which are the variables in the triple with the {@link RdfPredicate}
+ * specified in the constructor.
+ */
+ public RelationMapBuilder constraints(GraphPattern... constraints) {
+ this.constraints = constraints;
+ return this;
+ }
+
+ /**
+ * Indicates that the existence of the triple is not required, allowing to use the constraints to select certain
+ * subjects and to answer the mapping to an empty Set in the {@link RelationMapBuilder#buildOneToMany()} case and
+ * {@link RelationMapBuilder#NOTHING} in the {@link RelationMapBuilder#buildOneToOne()} case.
+ *
+ * @return the builder
+ */
+ public RelationMapBuilder relationIsOptional() {
+ this.isRelationOptional = true;
+ return this;
+ }
+
+ /**
+ * Indicates that the builder should use the triple's object for the key in the resulting {@link Map} instead of the
+ * subject (the default).
+ */
+ public RelationMapBuilder useRelationObjectAsKey() {
+ this.isSubjectKeyed = false;
+ return this;
+ }
+
+ /**
+ * Builds a One-to-One Map using the configuration of this builder. Throws an Exception if more than one values are
+ * found for a given key. If {@link #isRelationOptional} is true
+ * and no triple is found for the key, {@link #NOTHING} is set as the value.
+ */
+ public Map buildOneToOne() {
+ return makeTupleQueryBuilder()
+ .evaluateAndConvert()
+ .toMap(b -> getIRI(b, _relKey), this::getRelationValueOrNothing);
+ }
+
+ /**
+ * Builds a One-to-Many Map using the configuration of this builder.
+ */
+ public Map> buildOneToMany() {
+ return makeTupleQueryBuilder()
+ .evaluateAndConvert()
+ .mapAndCollect(
+ Function.identity(),
+ Collectors.toMap(
+ b -> getIRI(b, _relKey),
+ b -> getIRIOptional(b, _relValue)
+ .map(Set::of)
+ .orElseGet(Set::of),
+ RelationMapBuilder::mergeSets));
+ }
+
+ private static Set mergeSets(Set left, Set right) {
+ Set merged = new HashSet<>(left);
+ merged.addAll(right);
+ return merged;
+ }
+
+ private IRI getRelationValue(BindingSet b) {
+ if (isRelationOptional) {
+ return getIRIOptional(b, _relValue).orElse(NOTHING);
+ } else {
+ return getIRI(b, _relValue);
+ }
+ }
+
+ private IRI getRelationValueOrNothing(BindingSet b) {
+ if (isRelationOptional) {
+ return getIRIOptional(b, _relValue).orElse(NOTHING);
+ } else {
+ return getIRI(b, _relValue);
+ }
+ }
+
+ private TupleQueryEvaluationBuilder makeTupleQueryBuilder() {
+ return rdf4JTemplate
+ .tupleQuery(
+ Queries.SELECT(getProjection())
+ .where(getWhereClause())
+ .distinct()
+ .getQueryString())
+ .withBindings(bindingsBuilder.build());
+ }
+
+ private Projectable[] getProjection() {
+ if (this.isSubjectKeyed) {
+ return new Projectable[] {
+ SparqlBuilder.as(_relSubject, _relKey), SparqlBuilder.as(_relObject, _relValue)
+ };
+ } else {
+ return new Projectable[] {
+ SparqlBuilder.as(_relSubject, _relValue), SparqlBuilder.as(_relObject, _relKey)
+ };
+ }
+ }
+
+ private GraphPattern[] getWhereClause() {
+ TriplePattern tp = _relSubject.has(predicate, _relObject);
+ if (this.isRelationOptional) {
+ GraphPattern[] ret = new GraphPattern[constraints.length + 1];
+ ret[0] = tp.optional();
+ System.arraycopy(constraints, 0, ret, 1, constraints.length);
+ return ret;
+ } else {
+ return new GraphPattern[] { tp.and(constraints) };
+ }
+ }
+
+ public RelationMapBuilder withBinding(Variable key, Value value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, Value value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable key, Value value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, Value value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(Variable key, IRI value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, IRI value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable key, IRI value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable key, String value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, IRI value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(Variable key, String value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, String value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, String value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(Variable key, Integer value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, Integer value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable key, Integer value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, Integer value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(Variable key, Boolean value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, Boolean value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable key, Boolean value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, Boolean value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(Variable key, Float value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, Float value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable key, Float value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, Float value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(Variable key, Double value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBinding(String key, Double value) {
+ bindingsBuilder.add(key, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(Variable var, Double value) {
+ bindingsBuilder.addMaybe(var, value);
+ return this;
+ }
+
+ public RelationMapBuilder withBindingMaybe(String key, Double value) {
+ bindingsBuilder.addMaybe(key, value);
+ return this;
+ }
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/TupleQueryResultMapper.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/TupleQueryResultMapper.java
new file mode 100644
index 00000000000..beb86769c0a
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/TupleQueryResultMapper.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.support;
+
+import java.util.function.Function;
+
+import org.eclipse.rdf4j.query.TupleQueryResult;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public interface TupleQueryResultMapper extends Function {
+}
diff --git a/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/UpdateCallback.java b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/UpdateCallback.java
new file mode 100644
index 00000000000..b3f613200e7
--- /dev/null
+++ b/spring6-components/rdf4j-spring6/src/main/java/org/eclipse/rdf4j/spring/dao/support/UpdateCallback.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Eclipse RDF4J contributors.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+
+package org.eclipse.rdf4j.spring.dao.support;
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.eclipse.rdf4j.model.Value;
+
+/**
+ * @author Florian Kleedorfer
+ * @since 4.0.0
+ */
+public interface UpdateCallback extends Consumer