Skip to content

Commit ea06783

Browse files
authored
Refactor RSQL Utility - make it singleton and injectable (#2536)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
1 parent 7f97d6f commit ea06783

29 files changed

+231
-893
lines changed

hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/ql/utils/HawkbitQlToSql.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,10 @@
1919

2020
import org.eclipse.hawkbit.repository.RsqlQueryField;
2121
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
22-
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder;
23-
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder;
24-
import org.springframework.orm.jpa.vendor.Database;
22+
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder;
2523

2624
public class HawkbitQlToSql {
2725

28-
private static final Database DATABASE = Database.H2;
2926
private final EntityManager entityManager;
3027
private final boolean isEclipselink;
3128

@@ -61,16 +58,16 @@ private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(
6158
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final RsqlToSpecBuilder rsqlToSpecBuilder) {
6259
final CriteriaQuery<T> query = entityManager.getCriteriaBuilder().createQuery(domainClass);
6360
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
64-
final RsqlToSpecBuilder defaultRsqlToSpecBuilder = RsqlConfigHolder.getInstance().getRsqlToSpecBuilder();
61+
final RsqlToSpecBuilder defaultRsqlToSpecBuilder = RsqlUtility.getInstance().getRsqlToSpecBuilder();
6562
if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) {
66-
RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
63+
RsqlUtility.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
6764
}
6865
try {
69-
return query.where(RsqlUtility.<A, T> buildRsqlSpecification(rsql, fieldsClass, null, DATABASE)
66+
return query.where(RsqlUtility.getInstance().<A, T> buildRsqlSpecification(rsql, fieldsClass)
7067
.toPredicate(query.from(domainClass), cb.createQuery(domainClass), cb));
7168
} finally {
7269
if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) {
73-
RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(defaultRsqlToSpecBuilder);
70+
RsqlUtility.getInstance().setRsqlToSpecBuilder(defaultRsqlToSpecBuilder);
7471
}
7572
}
7673
}

hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlConfigHolder.java

Lines changed: 0 additions & 61 deletions
This file was deleted.

hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/RsqlUtility.java

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
*/
1010
package org.eclipse.hawkbit.repository.jpa.rsql;
1111

12-
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.G3;
13-
1412
import jakarta.persistence.EntityManager;
1513
import jakarta.persistence.criteria.CriteriaBuilder;
1614
import jakarta.persistence.criteria.CriteriaQuery;
1715

1816
import cz.jirutka.rsql.parser.RSQLParserException;
1917
import lombok.AccessLevel;
18+
import lombok.Getter;
2019
import lombok.NoArgsConstructor;
20+
import lombok.Setter;
2121
import lombok.extern.slf4j.Slf4j;
2222
import org.apache.commons.lang3.text.StrLookup;
2323
import org.eclipse.hawkbit.repository.RsqlQueryField;
@@ -27,6 +27,9 @@
2727
import org.eclipse.hawkbit.repository.jpa.rsql.legacy.SpecificationBuilderLegacy;
2828
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
2929
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.beans.factory.annotation.Value;
32+
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
3033
import org.springframework.data.jpa.domain.Specification;
3134
import org.springframework.orm.jpa.vendor.Database;
3235

@@ -64,34 +67,83 @@
6467
* $${OVERDUE_TS} would prevent the ${OVERDUE_TS} token from being expanded.
6568
*/
6669
@Slf4j
70+
@Getter
6771
@NoArgsConstructor(access = AccessLevel.PRIVATE)
68-
public final class RsqlUtility {
72+
public class RsqlUtility {
73+
74+
private static final RsqlUtility SINGLETON = new RsqlUtility();
75+
76+
public enum RsqlToSpecBuilder {
77+
LEGACY_G1, // legacy RSQL visitor
78+
LEGACY_G2, // G2 RSQL visitor
79+
G3 // G3 RSQL visitor - still experimental / yet default
80+
}
81+
82+
/**
83+
* If RSQL comparison operators shall ignore the case. If ignore case is <code>true</code> "x == ax" will match "x == aX"
84+
*/
85+
@Value("${hawkbit.rsql.ignore-case:true}")
86+
private boolean ignoreCase;
87+
/**
88+
* Declares if the database is case-insensitive, by default assumes <code>false</code>. In case it is case-sensitive and,
89+
* {@link #ignoreCase} is set to <code>true</code> the SQL queries use upper case comparisons to ignore case.
90+
* <p/>
91+
* If the database is declared as case-sensitive and ignoreCase is set to <code>false</code> the RSQL queries shall use strict
92+
* syntax - i.e. 'and' instead of 'AND' / 'aND'. Otherwise, the queries would be case-insensitive regarding operators.
93+
*/
94+
@Value("${hawkbit.rsql.case-insensitive-db:false}")
95+
private boolean caseInsensitiveDB;
96+
97+
/**
98+
* @deprecated in favour fixed final visitor / spec builder of G2 RSQL visitor / G3 spec builder. since 0.6.0
99+
*/
100+
@Setter // for tests only
101+
@Deprecated(forRemoval = true, since = "0.6.0")
102+
@Value("${hawkbit.rsql.rsql-to-spec-builder:G3}") //
103+
private RsqlToSpecBuilder rsqlToSpecBuilder;
104+
105+
private VirtualPropertyReplacer virtualPropertyReplacer;
106+
private Database database;
107+
private EntityManager entityManager;
108+
109+
/**
110+
* @return The holder singleton instance.
111+
*/
112+
public static RsqlUtility getInstance() {
113+
return SINGLETON;
114+
}
115+
116+
@Autowired(required = false)
117+
void setVirtualPropertyReplacer(final VirtualPropertyReplacer virtualPropertyReplacer) {
118+
this.virtualPropertyReplacer = virtualPropertyReplacer;
119+
}
120+
121+
@Autowired
122+
void setDatabase(final JpaProperties jpaProperties) {
123+
database = jpaProperties.getDatabase();
124+
}
125+
126+
@Autowired
127+
void setEntityManager(final EntityManager entityManager) {
128+
this.entityManager = entityManager;
129+
}
69130

70131
/**
71132
* Builds a JPA {@link Specification} which corresponds with the given RSQL query. The specification can be used to filter for JPA entities
72133
* with the given RSQL query.
73134
*
74135
* @param rsql the rsql query to be parsed
75136
* @param rsqlQueryFieldType the enum class type which implements the {@link RsqlQueryField}
76-
* @param virtualPropertyReplacer holds the logic how the known macros have to be resolved; may be <code>null</code>
77-
* @param database database in use
78137
* @return a specification which can be used with JPA
79138
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
80139
* given {@code fieldNameProvider}
81140
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
82141
*/
83-
public static <A extends Enum<A> & RsqlQueryField, T> Specification<T> buildRsqlSpecification(
84-
final String rsql, final Class<A> rsqlQueryFieldType,
85-
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
86-
if (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == G3) {
87-
return new SpecificationBuilder<T>(
88-
virtualPropertyReplacer,
89-
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase(),
90-
database)
91-
.specification(RsqlParser.parse(
92-
RsqlConfigHolder.getInstance().isCaseInsensitiveDB() || RsqlConfigHolder.getInstance().isIgnoreCase()
93-
? rsql.toLowerCase() : rsql,
94-
rsqlQueryFieldType));
142+
public <A extends Enum<A> & RsqlQueryField, T> Specification<T> buildRsqlSpecification(
143+
final String rsql, final Class<A> rsqlQueryFieldType) {
144+
if (rsqlToSpecBuilder == RsqlToSpecBuilder.G3) {
145+
return new SpecificationBuilder<T>(virtualPropertyReplacer, !caseInsensitiveDB && ignoreCase, database)
146+
.specification(RsqlParser.parse(caseInsensitiveDB || ignoreCase ? rsql.toLowerCase() : rsql, rsqlQueryFieldType));
95147
} else {
96148
return new SpecificationBuilderLegacy<A, T>(rsqlQueryFieldType, virtualPropertyReplacer, database).specification(rsql);
97149
}
@@ -101,18 +153,16 @@ public static <A extends Enum<A> & RsqlQueryField, T> Specification<T> buildRsql
101153
* Validates the RSQL string
102154
*
103155
* @param rsql RSQL string to validate
104-
* @param rsqlQueryFieldType
156+
* @param rsqlQueryFieldType the enum class type which implements the {@link RsqlQueryField}
157+
* @param jpaType the JPA entity type to validate against
105158
* @throws RSQLParserException if RSQL syntax is invalid
106159
* @throws RSQLParameterUnsupportedFieldException if RSQL key is not allowed
107160
*/
108161
@SuppressWarnings({ "unchecked", "rawtypes" })
109-
public static <A extends Enum<A> & RsqlQueryField> void validateRsqlFor(
110-
final String rsql, final Class<A> rsqlQueryFieldType,
111-
final Class<?> jpaType,
112-
final VirtualPropertyReplacer virtualPropertyReplacer, final EntityManager entityManager) {
162+
public <A extends Enum<A> & RsqlQueryField> void validateRsqlFor(
163+
final String rsql, final Class<A> rsqlQueryFieldType, final Class<?> jpaType) {
113164
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
114165
final CriteriaQuery<?> criteriaQuery = criteriaBuilder.createQuery(jpaType);
115-
buildRsqlSpecification(rsql, rsqlQueryFieldType, virtualPropertyReplacer, null)
116-
.toPredicate(criteriaQuery.from((Class) jpaType), criteriaQuery, criteriaBuilder);
166+
buildRsqlSpecification(rsql, rsqlQueryFieldType).toPredicate(criteriaQuery.from((Class) jpaType), criteriaQuery, criteriaBuilder);
117167
}
118168
}

hawkbit-repository/hawkbit-repository-jpa-ql/src/main/java/org/eclipse/hawkbit/repository/jpa/rsql/legacy/SpecificationBuilderLegacy.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
package org.eclipse.hawkbit.repository.jpa.rsql.legacy;
1111

1212
import static org.eclipse.hawkbit.repository.jpa.rsql.legacy.AbstractRSQLVisitor.OPERATORS;
13-
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
13+
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G1;
1414

1515
import java.util.List;
1616

@@ -24,7 +24,7 @@
2424
import org.eclipse.hawkbit.repository.RsqlQueryField;
2525
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
2626
import org.eclipse.hawkbit.repository.jpa.ql.SpecificationBuilder;
27-
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder;
27+
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
2828
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
2929
import org.springframework.data.jpa.domain.Specification;
3030
import org.springframework.orm.jpa.vendor.Database;
@@ -50,14 +50,14 @@ public SpecificationBuilderLegacy(
5050

5151
public Specification<T> specification(final String rsql) {
5252
return (root, query, cb) -> {
53-
final RsqlConfigHolder rsqlConfigHolder = RsqlConfigHolder.getInstance();
53+
final RsqlUtility rsqlUtility = RsqlUtility.getInstance();
5454

55-
final Node rootNode = parseRsql(rsql, rsqlConfigHolder);
55+
final Node rootNode = parseRsql(rsql, rsqlUtility);
5656
query.distinct(true);
5757

58-
final boolean ensureIgnoreCase = !rsqlConfigHolder.isCaseInsensitiveDB() && rsqlConfigHolder.isIgnoreCase();
58+
final boolean ensureIgnoreCase = !rsqlUtility.isCaseInsensitiveDB() && rsqlUtility.isIgnoreCase();
5959
final RSQLVisitor<List<Predicate>, String> jpqQueryRSQLVisitor =
60-
rsqlConfigHolder.getRsqlToSpecBuilder() == LEGACY_G1
60+
rsqlUtility.getRsqlToSpecBuilder() == LEGACY_G1
6161
? new JpaQueryRsqlVisitor<>(root, cb, rsqlQueryFieldType, virtualPropertyReplacer, database, query, ensureIgnoreCase)
6262
: new JpaQueryRsqlVisitorG2<>(rsqlQueryFieldType, root, query, cb, database, virtualPropertyReplacer, ensureIgnoreCase);
6363
final List<Predicate> accept = rootNode.accept(jpqQueryRSQLVisitor);
@@ -70,10 +70,10 @@ public Specification<T> specification(final String rsql) {
7070
};
7171
}
7272

73-
private static Node parseRsql(final String rsql, final RsqlConfigHolder rsqlConfigHolder) {
73+
private static Node parseRsql(final String rsql, final RsqlUtility rsqlUtility) {
7474
log.debug("Parsing rsql string {}", rsql);
7575
try {
76-
return new RSQLParser(OPERATORS).parse(rsqlConfigHolder.isCaseInsensitiveDB() || rsqlConfigHolder.isIgnoreCase() ? rsql.toLowerCase() : rsql);
76+
return new RSQLParser(OPERATORS).parse(rsqlUtility.isCaseInsensitiveDB() || rsqlUtility.isIgnoreCase() ? rsql.toLowerCase() : rsql);
7777
} catch (final IllegalArgumentException e) {
7878
throw new RSQLParameterSyntaxException("RSQL filter must not be null", e);
7979
} catch (final RSQLParserException e) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* SPDX-License-Identifier: EPL-2.0
99
*/
10-
package org.eclipse.hawkbit.repository.jpa.rsql.sa;
10+
package org.eclipse.hawkbit.repository.jpa.ql;
1111

1212
import java.util.Map;
1313
import java.util.Set;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
* SPDX-License-Identifier: EPL-2.0
99
*/
10-
package org.eclipse.hawkbit.repository.jpa.rsql.sa;
10+
package org.eclipse.hawkbit.repository.jpa.ql;
1111

1212
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
1313
import org.springframework.data.repository.CrudRepository;
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77
*
88
* SPDX-License-Identifier: EPL-2.0
99
*/
10-
package org.eclipse.hawkbit.repository.jpa.rsql.sa;
10+
package org.eclipse.hawkbit.repository.jpa.ql;
1111

12-
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
13-
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2;
12+
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G1;
13+
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder.LEGACY_G2;
1414

1515
import java.util.Arrays;
1616
import java.util.List;
1717

1818
import lombok.Getter;
1919
import lombok.extern.slf4j.Slf4j;
2020
import org.eclipse.hawkbit.repository.RsqlQueryField;
21+
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
22+
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility.RsqlToSpecBuilder;
2123
import org.eclipse.hawkbit.repository.jpa.rsql.legacy.SpecificationBuilderLegacy;
22-
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder;
23-
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder;
2424
import org.junit.jupiter.api.Test;
2525
import org.springframework.data.jpa.domain.Specification;
2626
import org.springframework.orm.jpa.vendor.Database;
@@ -32,12 +32,12 @@ class SpecificationBuilderLegacyTest extends SpecificationBuilderTest {
3232
private final SpecificationBuilderLegacy<RootField, Root> builder = new SpecificationBuilderLegacy<>(RootField.class, null, Database.H2);
3333

3434
private static void runWithRsqlToSpecBuilder(final Runnable runnable, final RsqlToSpecBuilder rsqlToSpecBuilder) {
35-
final RsqlToSpecBuilder defaultBuilder = RsqlConfigHolder.getInstance().getRsqlToSpecBuilder();
36-
RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
35+
final RsqlToSpecBuilder defaultBuilder = RsqlUtility.getInstance().getRsqlToSpecBuilder();
36+
RsqlUtility.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
3737
try {
3838
runnable.run();
3939
} finally {
40-
RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(defaultBuilder);
40+
RsqlUtility.getInstance().setRsqlToSpecBuilder(defaultBuilder);
4141
}
4242
}
4343

0 commit comments

Comments
 (0)