11package org .springframework .data .jpa .datatables .repository ;
22
33import java .util .ArrayList ;
4- import java .util .Arrays ;
54import java .util .List ;
65
7- import javax .persistence .criteria .CriteriaBuilder ;
8- import javax .persistence .criteria .CriteriaQuery ;
9- import javax .persistence .criteria .Expression ;
10- import javax .persistence .criteria .Fetch ;
11- import javax .persistence .criteria .From ;
12- import javax .persistence .criteria .JoinType ;
13- import javax .persistence .criteria .Predicate ;
14- import javax .persistence .criteria .Root ;
15- import javax .persistence .metamodel .Attribute .PersistentAttributeType ;
16-
176import org .springframework .data .domain .PageRequest ;
187import org .springframework .data .domain .Pageable ;
198import org .springframework .data .domain .Sort ;
2211import org .springframework .data .jpa .datatables .mapping .DataTablesInput ;
2312import org .springframework .data .jpa .datatables .parameter .ColumnParameter ;
2413import org .springframework .data .jpa .datatables .parameter .OrderParameter ;
25- import org .springframework .data .jpa .domain .Specification ;
26- import org .springframework .util .StringUtils ;
27-
28- import com .mysema .query .BooleanBuilder ;
29- import com .mysema .query .support .Expressions ;
30- import com .mysema .query .types .Ops ;
31- import com .mysema .query .types .expr .StringExpression ;
32- import com .mysema .query .types .path .PathBuilder ;
3314
3415public class DataTablesUtils {
3516
36- private final static String OR_SEPARATOR = "+" ;
37-
38- private final static String ATTRIBUTE_SEPARATOR = "." ;
39-
40- private final static char ESCAPE_CHAR = '\\' ;
41-
42- public static <T > Specification <T > getSpecification (Class <T > type , final DataTablesInput input ) {
43-
44- return new Specification <T >() {
45-
46- @ Override
47- public Predicate toPredicate (Root <T > root , CriteriaQuery <?> query ,
48- CriteriaBuilder criteriaBuilder ) {
49-
50- Predicate predicate = criteriaBuilder .conjunction ();
51-
52- // check for each searchable column whether a filter value
53- // exists
54- for (ColumnParameter column : input .getColumns ()) {
55- String filterValue = column .getSearch ().getValue ();
56- if (column .getSearchable () && StringUtils .hasText (filterValue )) {
57- Expression <String > expression = getExpression (root , column .getData ());
58-
59- if (filterValue .contains (OR_SEPARATOR )) {
60- // the filter contains multiple values, add a 'WHERE
61- // .. IN' clause
62- // Note: "\\" is added to escape special character
63- // '+'
64- String [] values = filterValue .split ("\\ " + OR_SEPARATOR );
65- if (values .length > 0 && isBoolean (values [0 ])) {
66- Object [] booleanValues = new Boolean [values .length ];
67- for (int i = 0 ; i < values .length ; i ++) {
68- booleanValues [i ] = Boolean .valueOf (values [i ]);
69- }
70- predicate =
71- criteriaBuilder .and (predicate , expression .as (Boolean .class ).in (booleanValues ));
72- } else {
73- predicate = criteriaBuilder .and (predicate , expression .in (Arrays .asList (values )));
74- }
75- } else {
76- // the filter contains only one value, add a 'WHERE
77- // .. LIKE' clause
78- if (isBoolean (filterValue )) {
79- predicate = criteriaBuilder .and (predicate , criteriaBuilder
80- .equal (expression .as (Boolean .class ), Boolean .valueOf (filterValue )));
81- } else {
82- predicate = criteriaBuilder .and (predicate ,
83- criteriaBuilder .like (criteriaBuilder .lower (expression ),
84- getLikeFilterValue (filterValue ), ESCAPE_CHAR ));
85- }
86- }
87- }
88- }
89-
90- // check whether a global filter value exists
91- String globalFilterValue = input .getSearch ().getValue ();
92- if (StringUtils .hasText (globalFilterValue )) {
93- Predicate matchOneColumnPredicate = criteriaBuilder .disjunction ();
94- // add a 'WHERE .. LIKE' clause on each searchable column
95- for (ColumnParameter column : input .getColumns ()) {
96- if (column .getSearchable ()) {
97- Expression <String > expression = getExpression (root , column .getData ());
98-
99- matchOneColumnPredicate = criteriaBuilder .or (matchOneColumnPredicate ,
100- criteriaBuilder .like (criteriaBuilder .lower (expression ),
101- getLikeFilterValue (globalFilterValue ), ESCAPE_CHAR ));
102- }
103- }
104- predicate = criteriaBuilder .and (predicate , matchOneColumnPredicate );
105- }
106- // findAll method does a count query first, and then query for the actual data. Yet in the
107- // count query, adding a JOIN FETCH results in the following error 'query specified join
108- // fetching, but the owner of the fetched association was not present in the select list'
109- // see https://jira.spring.io/browse/DATAJPA-105
110- boolean isCountQuery = query .getResultType () == Long .class ;
111- if (isCountQuery ) {
112- return predicate ;
113- }
114- // add JOIN FETCH when necessary
115- for (ColumnParameter column : input .getColumns ()) {
116- if (!column .getSearchable () || !column .getData ().contains (ATTRIBUTE_SEPARATOR )) {
117- continue ;
118- }
119- String [] values = column .getData ().split ("\\ " + ATTRIBUTE_SEPARATOR );
120- if (root .getModel ().getAttribute (values [0 ])
121- .getPersistentAttributeType () == PersistentAttributeType .EMBEDDED ) {
122- continue ;
123- }
124- Fetch <?, ?> fetch = null ;
125- for (int i = 0 ; i < values .length - 1 ; i ++) {
126- fetch = (fetch == null ? root : fetch ).fetch (values [i ], JoinType .LEFT );
127- }
128- }
129- return predicate ;
130- }
131-
132- };
133- }
134-
135- public static com .mysema .query .types .Predicate getPredicate (PathBuilder <?> entity ,
136- DataTablesInput input ) {
137-
138- BooleanBuilder predicate = new BooleanBuilder ();
139- // check for each searchable column whether a filter value exists
140- for (ColumnParameter column : input .getColumns ()) {
141- String filterValue = column .getSearch ().getValue ();
142- if (column .getSearchable () && StringUtils .hasText (filterValue )) {
143-
144- if (filterValue .contains (OR_SEPARATOR )) {
145- // the filter contains multiple values, add a 'WHERE .. IN'
146- // clause
147- // Note: "\\" is added to escape special character '+'
148- String [] values = filterValue .split ("\\ " + OR_SEPARATOR );
149- if (values .length > 0 && isBoolean (values [0 ])) {
150- List <Boolean > booleanValues = new ArrayList <Boolean >();
151- for (int i = 0 ; i < values .length ; i ++) {
152- booleanValues .add (Boolean .valueOf (values [i ]));
153- }
154- predicate = predicate .and (entity .getBoolean (column .getData ()).in (booleanValues ));
155- } else {
156- predicate .and (getStringExpression (entity , column .getData ()).in (values ));
157- }
158- } else {
159- // the filter contains only one value, add a 'WHERE .. LIKE'
160- // clause
161- if (isBoolean (filterValue )) {
162- predicate =
163- predicate .and (entity .getBoolean (column .getData ()).eq (Boolean .valueOf (filterValue )));
164- } else {
165- predicate = predicate .and (getStringExpression (entity , column .getData ()).lower ()
166- .like (getLikeFilterValue (filterValue ), ESCAPE_CHAR ));
167- }
168- }
169- }
170- }
171-
172- // check whether a global filter value exists
173- String globalFilterValue = input .getSearch ().getValue ();
174- if (StringUtils .hasText (globalFilterValue )) {
175- BooleanBuilder matchOneColumnPredicate = new BooleanBuilder ();
176- // add a 'WHERE .. LIKE' clause on each searchable column
177- for (ColumnParameter column : input .getColumns ()) {
178- if (column .getSearchable ()) {
179- matchOneColumnPredicate =
180- matchOneColumnPredicate .or (getStringExpression (entity , column .getData ()).lower ()
181- .like (getLikeFilterValue (globalFilterValue ), ESCAPE_CHAR ));
182- }
183- }
184- predicate = predicate .and (matchOneColumnPredicate );
185- }
186- return predicate ;
187- }
17+ public final static String OR_SEPARATOR = "+" ;
18+ public final static String ESCAPED_OR_SEPARATOR = "\\ +" ;
19+ public final static String ATTRIBUTE_SEPARATOR = "." ;
20+ public final static String ESCAPED_ATTRIBUTE_SEPARATOR = "\\ ." ;
21+ public final static char ESCAPE_CHAR = '\\' ;
18822
18923 /**
19024 * Creates a 'LIMIT .. OFFSET .. ORDER BY ..' clause for the given {@link DataTablesInput}.
@@ -211,38 +45,14 @@ public static Pageable getPageable(DataTablesInput input) {
21145 return new PageRequest (input .getStart () / input .getLength (), input .getLength (), sort );
21246 }
21347
214- private static boolean isBoolean (String filterValue ) {
48+ public static boolean isBoolean (String filterValue ) {
21549 return "TRUE" .equalsIgnoreCase (filterValue ) || "FALSE" .equalsIgnoreCase (filterValue );
21650 }
21751
218- private static Expression <String > getExpression (Root <?> root , String columnData ) {
219- if (columnData .contains (ATTRIBUTE_SEPARATOR )) {
220- // columnData is like "joinedEntity.attribute" so add a join clause
221- String [] values = columnData .split ("\\ " + ATTRIBUTE_SEPARATOR );
222- if (root .getModel ().getAttribute (values [0 ])
223- .getPersistentAttributeType () == PersistentAttributeType .EMBEDDED ) {
224- // with @Embedded attribute
225- return root .get (values [0 ]).get (values [1 ]).as (String .class );
226- }
227- From <?, ?> from = root ;
228- for (int i = 0 ; i < values .length - 1 ; i ++) {
229- from = from .join (values [i ], JoinType .LEFT );
230- }
231- return from .get (values [values .length - 1 ]).as (String .class );
232- } else {
233- // columnData is like "attribute" so nothing particular to do
234- return root .get (columnData ).as (String .class );
235- }
236- }
237-
238- private static String getLikeFilterValue (String filterValue ) {
52+ public static String getLikeFilterValue (String filterValue ) {
23953 return "%"
24054 + filterValue .toLowerCase ().replaceAll ("%" , "\\ \\ " + "%" ).replaceAll ("_" , "\\ \\ " + "_" )
24155 + "%" ;
24256 }
24357
244- private static StringExpression getStringExpression (PathBuilder <?> entity , String columnData ) {
245- return Expressions .stringOperation (Ops .STRING_CAST , entity .get (columnData ));
246- }
247-
24858}
0 commit comments