Skip to content

Commit 590bacb

Browse files
committed
Rename SpecificationFactory to JpaSpecifications
1 parent 37cc591 commit 590bacb

File tree

4 files changed

+106
-37
lines changed

4 files changed

+106
-37
lines changed

project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/dataset/LocalRawDatasetRepositoryImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package life.qbic.projectmanagement.infrastructure.dataset;
22

33
import static life.qbic.logging.service.LoggerFactory.logger;
4-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.distinct;
5-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.exactMatches;
6-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.jsonContains;
7-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.propertyContains;
4+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.distinct;
5+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.exactMatches;
6+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.jsonContains;
7+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.propertyContains;
88
import static org.springframework.data.jpa.domain.Specification.allOf;
99
import static org.springframework.data.jpa.domain.Specification.anyOf;
1010

project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/experiment/measurement/jpa/NgsMeasurementJpaRepository.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package life.qbic.projectmanagement.infrastructure.experiment.measurement.jpa;
22

3-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.CUSTOM_DATE_TIME_PATTERN;
4-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.contains;
5-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.distinct;
6-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.formattedClientTimeContains;
7-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.jsonContains;
8-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.propertyContains;
3+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.CUSTOM_DATE_TIME_PATTERN;
4+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.contains;
5+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.distinct;
6+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.formattedClientTimeContains;
7+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.jsonContains;
8+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.propertyContains;
99

1010
import com.fasterxml.jackson.core.JacksonException;
1111
import com.fasterxml.jackson.core.JsonParseException;

project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/experiment/measurement/jpa/PxpMeasurementJpaRepository.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package life.qbic.projectmanagement.infrastructure.experiment.measurement.jpa;
22

3-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.contains;
4-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.distinct;
5-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.formattedClientTimeContains;
6-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.jsonContains;
7-
import static life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory.propertyContains;
3+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.contains;
4+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.distinct;
5+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.formattedClientTimeContains;
6+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.jsonContains;
7+
import static life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications.propertyContains;
88

99
import com.fasterxml.jackson.core.JsonParseException;
1010
import com.fasterxml.jackson.core.JsonParser;
@@ -48,7 +48,7 @@
4848
import life.qbic.projectmanagement.infrastructure.PreventAnyUpdateEntityListener;
4949
import life.qbic.projectmanagement.infrastructure.experiment.measurement.jpa.PxpMeasurementJpaRepository.MsDevice.MsDeviceReadConverter;
5050
import life.qbic.projectmanagement.infrastructure.experiment.measurement.jpa.PxpMeasurementJpaRepository.PxpMeasurementInformation;
51-
import life.qbic.projectmanagement.infrastructure.jpa.SpecificationFactory;
51+
import life.qbic.projectmanagement.infrastructure.jpa.JpaSpecifications;
5252
import org.hibernate.collection.spi.PersistentBag;
5353
import org.springframework.boot.jackson.JsonComponent;
5454
import org.springframework.data.convert.ReadingConverter;
@@ -183,7 +183,7 @@ public Specification<PxpMeasurementInformation> asSpecification() {
183183
.get("iri").as(String.class),
184184
searchTerm),
185185
formattedClientTimeContains("registeredAt", searchTerm, timeZoneOffsetMillis,
186-
SpecificationFactory.CUSTOM_DATE_TIME_PATTERN),
186+
JpaSpecifications.CUSTOM_DATE_TIME_PATTERN),
187187
contains(root -> getSampleInfos(root)
188188
.get("sampleLabel").as(String.class), searchTerm),
189189
contains(root -> getSampleInfos(root)

project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/jpa/SpecificationFactory.java renamed to project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/jpa/JpaSpecifications.java

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,104 @@
1313
import life.qbic.logging.service.LoggerFactory;
1414
import org.springframework.data.jpa.domain.Specification;
1515

16+
1617
/**
17-
* A factory for {@link Specification} based on commonly required parameters.
18+
* Utility class providing reusable helper methods for constructing Spring Data JPA
19+
* {@link org.springframework.data.jpa.domain.Specification} instances.
20+
*
21+
* <h2>Intention</h2>
22+
* <p>
23+
* This class centralizes common patterns for building query predicates based on entity
24+
* properties, property names, or expressions. It is designed as a collection of small,
25+
* reusable building blocks that simplify working with the JPA Criteria API and reduce
26+
* boilerplate code across the persistence layer.
27+
* </p>
28+
*
29+
* <h2>Relation to JPA Specifications</h2>
30+
* <p>
31+
* Spring Data JPA {@link org.springframework.data.jpa.domain.Specification} follows the
32+
* Composite pattern, allowing complex queries to be built by combining smaller predicates
33+
* using {@code and(...)} and {@code or(...)}. The methods in this class act as reusable
34+
* "leaf" specifications that can be composed into more complex query structures.
35+
* </p>
36+
*
37+
* <p>
38+
* This class is intentionally stateless and focuses solely on supporting the creation of
39+
* composable {@code Specification} instances that can be assembled where needed.
40+
* </p>
1841
*/
19-
public class SpecificationFactory {
2042

21-
private static final Logger log = LoggerFactory.logger(SpecificationFactory.class);
43+
public class JpaSpecifications {
44+
45+
private static final Logger log = LoggerFactory.logger(JpaSpecifications.class);
2246
/**
2347
* A custom date time format not in any java.time ISO formats
2448
*/
2549
public static final String CUSTOM_DATE_TIME_PATTERN = "%Y-%m-%d %H:%i";
2650

2751

2852
/**
29-
* Extracts an {@link Expression} of type {@link T }from another {@link Expression} of type {@link S}
30-
* @param <S> The type of item used in the expression
31-
* @param <T> the desired type of expression after extraction
32-
* @param <X> the concrete type of source Expression
53+
* Interface for resolving a JPA {@link jakarta.persistence.criteria.Expression}
54+
* from a given source expression.
55+
*
56+
* <h2>Intention</h2>
57+
* <p>
58+
* An {@code ExpressionProvider} describes how to navigate from an existing JPA Criteria
59+
* expression (for example a {@code Root}, {@code From}, or another {@code Expression})
60+
* to a target expression representing a specific attribute or derived value.
61+
* </p>
62+
*
63+
* <p>
64+
* It does not extract runtime values from entities. Instead, it defines how to build
65+
* a new {@link jakarta.persistence.criteria.Expression} that can later be used to
66+
* construct predicates in a {@link org.springframework.data.jpa.domain.Specification}.
67+
* </p>
68+
*
69+
* <h2>Usage context</h2>
70+
* <p>
71+
* This abstraction is especially useful when building reusable specification helpers.
72+
* Instead of referring to entity attributes via string-based paths, callers can provide
73+
* an {@code ExpressionProvider} that encapsulates how to access or derive the relevant
74+
* expression. This improves readability, reuse, and type-safety when constructing
75+
* dynamic queries with the JPA Criteria API.
76+
* </p>
77+
*
78+
* @param <S> the type represented by the source type (e.g. MyEntity)
79+
* @param <T> the type represented by the resolved expression (e.g. String)
80+
* @param <X> the source type from which the expression is resolved (e.g. Root<MyEntity>)
3381
*/
34-
public interface Extractor<S, T, X extends Expression<S>> extends Function<X, Expression<T>> {
82+
@FunctionalInterface
83+
public interface ExpressionProvider<S, T, X extends Expression<S>> extends
84+
Function<X, Expression<T>> {
3585

3686
@Override
3787
default Expression<T> apply(X expression) {
3888
return extractFrom(expression);
3989
}
4090

4191
/**
42-
* Extract an expression from the provided expression.
92+
* Resolves a target {@link jakarta.persistence.criteria.Expression} from the given source
93+
* expression.
94+
*
95+
* <p>
96+
* The source is typically a {@code Root<T>}, {@code From<?, ?>}, or another expression
97+
* that serves as the starting point for navigating to an attribute. Implementations
98+
* define how to derive the resulting expression, for example by:
99+
* </p>
100+
* <ul>
101+
* <li>Accessing an entity attribute</li>
102+
* <li>Navigating a join path</li>
103+
* <li>Returning a nested property expression</li>
104+
* <li>Applying a transformation or function</li>
105+
* </ul>
106+
*
107+
* <p>
108+
* The returned expression can then be used to build predicates such as equality,
109+
* LIKE, range, or null checks within a {@link org.springframework.data.jpa.domain.Specification}.
110+
* </p>
43111
*
44-
* @param expression the provided expression
45-
* @return the extracted expression
112+
* @param expression the source expression from which the target expression should be derived
113+
* @return the resolved expression representing the desired attribute or value
46114
*/
47115
Expression<T> extractFrom(X expression);
48116

@@ -70,32 +138,32 @@ public static <T> Specification<T> propertyContains(String propertyName, String
70138

71139
/**
72140
* Creates a {@link Specification} that passes when value of an {@link Expression} contains the search term.
73-
* @param extractor an {@link Extractor} that provides an {@link Expression} of type {@link String}
141+
* @param expressionProvider an {@link ExpressionProvider} that provides an {@link Expression} of type {@link String}
74142
* @param searchTerm the term to search for
75143
* @return a configured Specification
76144
* @param <T> the type of the {@link Specification} {@link Root}
77145
*/
78146
public static <T> Specification<T> contains(
79-
Extractor<T, String, Root<T>> extractor,
147+
ExpressionProvider<T, String, Root<T>> expressionProvider,
80148
String searchTerm) {
81149
return (root, query, criteriaBuilder) ->
82-
contains(criteriaBuilder, extractor.extractFrom(root), searchTerm);
150+
contains(criteriaBuilder, expressionProvider.extractFrom(root), searchTerm);
83151
}
84152

85153
/**
86154
* Creates a {@link Specification} that passes when value of an {@link Expression} equals the provided value.
87-
* @param extractor an {@link Extractor} that provides an {@link Expression} of type {@link S}
155+
* @param expressionProvider an {@link ExpressionProvider} that provides an {@link Expression} of type {@link S}
88156
* @param other the object to compare to
89157
* @return a configured Specification
90158
* @param <S> the type of item that is compared for equality
91159
* @param <T> the type of the {@link Specification} {@link Root}
92160
*/
93161
public static <S, T> Specification<T> exactMatches(
94-
Extractor<T, S, Root<T>> extractor,
162+
ExpressionProvider<T, S, Root<T>> expressionProvider,
95163
S other
96164
) {
97165
return (root, query, criteriaBuilder) ->
98-
objectEquals(criteriaBuilder, extractor.extractFrom(root), other);
166+
objectEquals(criteriaBuilder, expressionProvider.extractFrom(root), other);
99167
}
100168

101169
/**
@@ -125,18 +193,19 @@ public static <T> Specification<T> formattedClientTimeContains(String instantPro
125193

126194
/**
127195
* Creates a {@link Specification} that passes when an {@link Expression} resulting in a JSON contains the search term at the specified JSON path.
128-
* @param extractor an {@link Extractor} that provides an {@link Expression} of type {@link S}
196+
* @param expressionProvider an {@link ExpressionProvider} that provides an {@link Expression} of type {@link S}
129197
* @param jsonPath json path to be searched
130198
* @param searchTerm the term to search for
131199
* @return a configured Specification
132200
* @param <S> the type of item that is compared for equality
133201
* @param <T> the type of the {@link Specification} {@link Root}
134202
* @see <a href="https://mariadb.com/docs/server/reference/sql-functions/special-functions/json-functions/jsonpath-expressions">JSONPath Expressions</a>
135203
*/
136-
public static <S, T> Specification<S> jsonContains(Extractor<S, T, Root<S>> extractor,
204+
public static <S, T> Specification<S> jsonContains(
205+
ExpressionProvider<S, T, Root<S>> expressionProvider,
137206
String jsonPath, String searchTerm) {
138207
return (root, query, criteriaBuilder) ->
139-
jsonContains(criteriaBuilder, extractor.extractFrom(root), jsonPath, searchTerm);
208+
jsonContains(criteriaBuilder, expressionProvider.extractFrom(root), jsonPath, searchTerm);
140209

141210
}
142211

0 commit comments

Comments
 (0)