1
- /**
2
- * Copyright (c) 2025, RTE (http://www.rte-france.com)
3
- * This Source Code Form is subject to the terms of the Mozilla Public
4
- * License, v. 2.0. If a copy of the MPL was not distributed with this
5
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
- */
7
-
8
1
/**
9
2
* Copyright (c) 2025, RTE (http://www.rte-france.com)
10
3
* This Source Code Form is subject to the terms of the Mozilla Public
33
26
* @author Kevin Le Saulnier <[email protected] >
34
27
*/
35
28
public final class SpecificationUtils {
36
- /**
37
- * Maximum values per IN clause chunk to avoid StackOverflow exceptions.
38
- * Current value (500) is a safe default but can be changed
39
- */
40
- public static final int MAX_IN_CLAUSE_SIZE = 500 ;
41
29
42
30
public static final String FIELD_SEPARATOR = "." ;
43
31
32
+ /**
33
+ Prefer less than 1000 for Oracle DB / good compromise for Postgres
34
+ */
35
+ public static final int MAX_IN_CLAUSE_SIZE = 10000 ;
36
+
44
37
// Utility class, so no constructor
45
38
private SpecificationUtils () { }
46
39
@@ -58,6 +51,11 @@ public static <X> Specification<X> notEqual(String field, String value) {
58
51
return (root , cq , cb ) -> cb .notEqual (getColumnPath (root , field ), value );
59
52
}
60
53
54
+ public static <X > Specification <X > in (String field , List <String > values ) {
55
+ return (root , cq , cb ) ->
56
+ cb .upper (getColumnPath (root , field ).as (String .class )).in (values );
57
+ }
58
+
61
59
public static <X > Specification <X > contains (String field , String value ) {
62
60
return (root , cq , cb ) -> cb .like (cb .upper (getColumnPath (root , field ).as (String .class )), "%" + EscapeCharacter .DEFAULT .escape (value ).toUpperCase () + "%" , EscapeCharacter .DEFAULT .getEscapeCharacter ());
63
61
}
@@ -142,15 +140,22 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio
142
140
// implicitly an IN resourceFilter type because only IN may have value lists as filter value
143
141
List <String > inValues = valueList .stream ()
144
142
.map (Object ::toString )
143
+ .map (String ::toUpperCase )
145
144
.toList ();
146
145
completedSpecification = completedSpecification .and (
146
+ resourceFilter .type () == ResourceFilterDTO .Type .NOT_EQUAL ?
147
+ not (generateInSpecification (resourceFilter .column (), inValues )) :
147
148
generateInSpecification (resourceFilter .column (), inValues )
148
149
);
149
150
} else if (resourceFilter .value () == null ) {
150
151
// if the value is null, we build an impossible specification (trick to remove later on ?)
151
152
completedSpecification = completedSpecification .and (not (completedSpecification ));
152
153
} else {
153
- completedSpecification = completedSpecification .and (equals (resourceFilter .column (), resourceFilter .value ().toString ()));
154
+ completedSpecification = completedSpecification .and (
155
+ resourceFilter .type () == ResourceFilterDTO .Type .NOT_EQUAL ?
156
+ notEqual (resourceFilter .column (), resourceFilter .value ().toString ()) :
157
+ equals (resourceFilter .column (), resourceFilter .value ().toString ())
158
+ );
154
159
}
155
160
}
156
161
case CONTAINS -> {
@@ -183,32 +188,17 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio
183
188
* @return a specification for the IN clause
184
189
*/
185
190
private static <X > Specification <X > generateInSpecification (String column , List <String > inPossibleValues ) {
186
-
187
- if (inPossibleValues .size () > MAX_IN_CLAUSE_SIZE ) {
188
- // there are too many values for only one call to anyOf() : it might cause a StackOverflow
189
- // => the specification is divided into several specifications which have an OR between them :
190
- List <List <String >> chunksOfInValues = Lists .partition (inPossibleValues , MAX_IN_CLAUSE_SIZE );
191
- Specification <X > containerSpec = null ;
192
- for (List <String > chunk : chunksOfInValues ) {
193
- Specification <X > multiOrEqualSpec = anyOf (
194
- chunk
195
- .stream ()
196
- .map (value -> SpecificationUtils .<X >equals (column , value ))
197
- .toList ()
198
- );
199
- if (containerSpec == null ) {
200
- containerSpec = multiOrEqualSpec ;
201
- } else {
202
- containerSpec = containerSpec .or (multiOrEqualSpec );
203
- }
191
+ List <List <String >> chunksOfInValues = Lists .partition (inPossibleValues , MAX_IN_CLAUSE_SIZE );
192
+ Specification <X > containerSpec = null ;
193
+ for (List <String > chunk : chunksOfInValues ) {
194
+ Specification <X > multiOrEqualSpec = Specification .anyOf (in (column , chunk ));
195
+ if (containerSpec == null ) {
196
+ containerSpec = multiOrEqualSpec ;
197
+ } else {
198
+ containerSpec = containerSpec .or (multiOrEqualSpec );
204
199
}
205
- return containerSpec ;
206
200
}
207
- return anyOf (inPossibleValues
208
- .stream ()
209
- .map (value -> SpecificationUtils .<X >equals (column , value ))
210
- .toList ()
211
- );
201
+ return containerSpec ;
212
202
}
213
203
214
204
@ NotNull
@@ -236,6 +226,8 @@ private static <X> Specification<X> appendNumberFilterToSpecification(Specificat
236
226
specification .and (lessThanOrEqual (resourceFilter .column (), valueDouble , tolerance ));
237
227
case GREATER_THAN_OR_EQUAL ->
238
228
specification .and (greaterThanOrEqual (resourceFilter .column (), valueDouble , tolerance ));
229
+ case EQUALS -> specification .and (greaterThanOrEqual (resourceFilter .column (), valueDouble , tolerance ))
230
+ .and (lessThanOrEqual (resourceFilter .column (), valueDouble , tolerance ));
239
231
default ->
240
232
throw new IllegalArgumentException ("The filter type " + resourceFilter .type () + " is not supported with the data type " + resourceFilter .dataType ());
241
233
};
0 commit comments