9
9
*/
10
10
package org .eclipse .hawkbit .repository .jpa .rsql ;
11
11
12
- import static org .eclipse .hawkbit .repository .jpa .rsql .RsqlConfigHolder .RsqlToSpecBuilder .G3 ;
13
-
14
12
import jakarta .persistence .EntityManager ;
15
13
import jakarta .persistence .criteria .CriteriaBuilder ;
16
14
import jakarta .persistence .criteria .CriteriaQuery ;
17
15
18
16
import cz .jirutka .rsql .parser .RSQLParserException ;
19
17
import lombok .AccessLevel ;
18
+ import lombok .Getter ;
20
19
import lombok .NoArgsConstructor ;
20
+ import lombok .Setter ;
21
21
import lombok .extern .slf4j .Slf4j ;
22
22
import org .apache .commons .lang3 .text .StrLookup ;
23
23
import org .eclipse .hawkbit .repository .RsqlQueryField ;
27
27
import org .eclipse .hawkbit .repository .jpa .rsql .legacy .SpecificationBuilderLegacy ;
28
28
import org .eclipse .hawkbit .repository .rsql .VirtualPropertyReplacer ;
29
29
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 ;
30
33
import org .springframework .data .jpa .domain .Specification ;
31
34
import org .springframework .orm .jpa .vendor .Database ;
32
35
64
67
* $${OVERDUE_TS} would prevent the ${OVERDUE_TS} token from being expanded.
65
68
*/
66
69
@ Slf4j
70
+ @ Getter
67
71
@ 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
+ }
69
130
70
131
/**
71
132
* Builds a JPA {@link Specification} which corresponds with the given RSQL query. The specification can be used to filter for JPA entities
72
133
* with the given RSQL query.
73
134
*
74
135
* @param rsql the rsql query to be parsed
75
136
* @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
78
137
* @return a specification which can be used with JPA
79
138
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
80
139
* given {@code fieldNameProvider}
81
140
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
82
141
*/
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 ));
95
147
} else {
96
148
return new SpecificationBuilderLegacy <A , T >(rsqlQueryFieldType , virtualPropertyReplacer , database ).specification (rsql );
97
149
}
@@ -101,18 +153,16 @@ public static <A extends Enum<A> & RsqlQueryField, T> Specification<T> buildRsql
101
153
* Validates the RSQL string
102
154
*
103
155
* @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
105
158
* @throws RSQLParserException if RSQL syntax is invalid
106
159
* @throws RSQLParameterUnsupportedFieldException if RSQL key is not allowed
107
160
*/
108
161
@ 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 ) {
113
164
final CriteriaBuilder criteriaBuilder = entityManager .getCriteriaBuilder ();
114
165
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 );
117
167
}
118
168
}
0 commit comments