diff --git a/spring-components/rdf4j-spring/src/main/java/org/eclipse/rdf4j/spring/dao/support/join/JoinQuery.java b/spring-components/rdf4j-spring/src/main/java/org/eclipse/rdf4j/spring/dao/support/join/JoinQuery.java new file mode 100644 index 00000000000..971e3369165 --- /dev/null +++ b/spring-components/rdf4j-spring/src/main/java/org/eclipse/rdf4j/spring/dao/support/join/JoinQuery.java @@ -0,0 +1,38 @@ +package org.eclipse.rdf4j.spring.dao.support.join; + +import java.util.function.Supplier; + +import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder; +import org.eclipse.rdf4j.sparqlbuilder.core.Variable; +import org.eclipse.rdf4j.spring.support.RDF4JTemplate; + +/** + * Creates a reusable {@link org.eclipse.rdf4j.query.TupleQuery} (and takes care of it getting reused properly using + * {@link RDF4JTemplate#tupleQuery(Class, String, Supplier)}). + * + *
+ * The JoinQuery is created using the {@link JoinQueryBuilder}. + * + *
+ * To set bindings and execute a {@link JoinQuery}, obtain the {@link JoinQueryEvaluationBuilder} via + * {@link #evaluationBuilder(RDF4JTemplate)}. + */ +public class JoinQuery { + + public static final Variable _sourceEntity = SparqlBuilder.var("sourceEntity"); + public static final Variable _targetEntity = SparqlBuilder.var("targetEntity"); + + private final String queryString; + + JoinQuery(JoinQueryBuilder joinQueryBuilder) { + this.queryString = joinQueryBuilder.makeQueryString(); + } + + public JoinQueryEvaluationBuilder evaluationBuilder(RDF4JTemplate rdf4JTemplate) { + return new JoinQueryEvaluationBuilder( + rdf4JTemplate.tupleQuery( + getClass(), + this.getClass().getName() + "@" + this.hashCode(), + () -> this.queryString)); + } +} diff --git a/spring-components/rdf4j-spring/src/main/java/org/eclipse/rdf4j/spring/dao/support/join/JoinQueryBuilder.java b/spring-components/rdf4j-spring/src/main/java/org/eclipse/rdf4j/spring/dao/support/join/JoinQueryBuilder.java new file mode 100644 index 00000000000..8eda3a50eb3 --- /dev/null +++ b/spring-components/rdf4j-spring/src/main/java/org/eclipse/rdf4j/spring/dao/support/join/JoinQueryBuilder.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * 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.join; + +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri; + +import java.util.function.Function; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.sparqlbuilder.constraint.propertypath.builder.PropertyPathBuilder; +import org.eclipse.rdf4j.sparqlbuilder.core.Projectable; +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.rdf.RdfPredicate; +import org.eclipse.rdf4j.spring.support.RDF4JTemplate; + +/** + * Builder for the {@link JoinQuery}. Allows for building the JoinQuery object directly via + * {@link #build(RDF4JTemplate)}, and for building a lazy initizalizer via {@link #buildLazyInitializer()}. + * + *
+ * You would use the lazy initializer like so: + * + *
+ * public class MyDao extends RDF4JDAO { + * // ... + * + * private static final LazyJoinQueryInitizalizer lazyJoinQuery = JoinQueryBuilder.of(SKOS.broader) + * // .. configure your join + * .buildLazyInitializer(); + * + * public Map+ */ +public class JoinQueryBuilder { + + private final RdfPredicate predicate; + private GraphPattern subjectConstraints = null; + private GraphPattern objectConstraints = null; + private JoinType joinType = JoinType.INNER; + + private JoinQueryBuilder(RdfPredicate predicate) { + this.predicate = predicate; + } + + private JoinQueryBuilder(IRI predicate) { + this.predicate = iri(predicate); + } + + public static JoinQueryBuilder of(RdfPredicate rdfPredicate) { + return new JoinQueryBuilder(rdfPredicate); + } + + public static JoinQueryBuilder of(IRI predicate) { + return new JoinQueryBuilder(predicate); + } + + public static JoinQueryBuilder of( + RDF4JTemplate rdf4JTemplate, PropertyPathBuilder propertyPathBuilder) { + return new JoinQueryBuilder(() -> propertyPathBuilder.build().getQueryString()); + } + + public JoinQueryBuilder sourceEntityConstraints( + Function> getJoinedData(IRI sourceEntityId) { + * return lazyJoinQuery.get(getRdf4JTemplate()) + * .withSourceEntityIdBinding(sourceEntityId) + * .buildOneToMany(); + * } + * + * } + * + *
+ * Obtained via {@link JoinQuery#evaluationBuilder(RDF4JTemplate)}. To use a {@link JoinQueryEvaluationBuilder}: + * + *
+ * This construct is thread-safe because the JoinQuery's internal state does not change after initialization; rather, + * when used to perform actual queries, it re-uses or creates a reusable TupleQuery using the {@link RDF4JTemplate}, and + * uses a {@link JoinQueryEvaluationBuilder} object to encapsulate per-evaluation state. + * + *
+ * Usually, you would assign this to a static member of a class and obtain the value in an instance method.
+ */
+public class LazyJoinQueryInitizalizer {
+ private JoinQueryBuilder joinQueryBuilder;
+ private JoinQuery joinQuery;
+
+ LazyJoinQueryInitizalizer(JoinQueryBuilder joinQueryBuilder) {
+ this.joinQueryBuilder = joinQueryBuilder;
+ }
+
+ public JoinQueryEvaluationBuilder get(RDF4JTemplate rdf4JTemplate) {
+ if (joinQuery != null) {
+ return joinQuery.evaluationBuilder(rdf4JTemplate);
+ } else {
+ synchronized (this) {
+ if (this.joinQuery == null) {
+ this.joinQuery = this.joinQueryBuilder.build();
+ }
+ }
+ }
+ return joinQuery.evaluationBuilder(rdf4JTemplate);
+ }
+}
diff --git a/spring-components/rdf4j-spring/src/test/java/org/eclipse/rdf4j/spring/dao/support/ServiceLayerTests.java b/spring-components/rdf4j-spring/src/test/java/org/eclipse/rdf4j/spring/dao/support/ServiceLayerTests.java
index 9cfc3b007d3..dcac9146049 100644
--- a/spring-components/rdf4j-spring/src/test/java/org/eclipse/rdf4j/spring/dao/support/ServiceLayerTests.java
+++ b/spring-components/rdf4j-spring/src/test/java/org/eclipse/rdf4j/spring/dao/support/ServiceLayerTests.java
@@ -17,8 +17,12 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.Set;
+
+import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.spring.RDF4JSpringTestBase;
import org.eclipse.rdf4j.spring.domain.model.Artist;
+import org.eclipse.rdf4j.spring.domain.model.EX;
import org.eclipse.rdf4j.spring.domain.model.Painting;
import org.eclipse.rdf4j.spring.domain.service.ArtService;
import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
@@ -30,6 +34,8 @@
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionTemplate;
+import shaded_package.org.bouncycastle.asn1.tsp.ArchiveTimeStamp;
+
/**
* @author Florian Kleedorfer
* @since 4.0.0
@@ -97,4 +103,15 @@ public void testRollbackOnException() {
return null;
});
}
+
+ @Test
+ public void testGetPaintingsOfArtist() {
+ transactionTemplate.execute(status -> {
+ Set