Skip to content

Commit 1f8e7a8

Browse files
Minor refactoring (darrachequesne#22)
1 parent c723eee commit 1f8e7a8

File tree

5 files changed

+234
-203
lines changed

5 files changed

+234
-203
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.springframework.data.jpa.datatables.qrepository;
2+
3+
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPED_OR_SEPARATOR;
4+
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPE_CHAR;
5+
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.OR_SEPARATOR;
6+
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.getLikeFilterValue;
7+
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.isBoolean;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import org.springframework.data.jpa.datatables.mapping.DataTablesInput;
13+
import org.springframework.data.jpa.datatables.parameter.ColumnParameter;
14+
import org.springframework.util.StringUtils;
15+
16+
import com.mysema.query.BooleanBuilder;
17+
import com.mysema.query.support.Expressions;
18+
import com.mysema.query.types.Ops;
19+
import com.mysema.query.types.Predicate;
20+
import com.mysema.query.types.expr.StringExpression;
21+
import com.mysema.query.types.path.PathBuilder;
22+
23+
class PredicateFactory {
24+
25+
public static Predicate createPredicate(PathBuilder<?> entity, DataTablesInput input) {
26+
BooleanBuilder predicate = new BooleanBuilder();
27+
28+
// check for each searchable column whether a filter value exists
29+
for (ColumnParameter column : input.getColumns()) {
30+
String filterValue = column.getSearch().getValue();
31+
boolean isColumnSearchable = column.getSearchable() && StringUtils.hasText(filterValue);
32+
if (!isColumnSearchable) {
33+
continue;
34+
}
35+
if (filterValue.contains(OR_SEPARATOR)) {
36+
// the filter contains multiple values, add a 'WHERE .. IN' clause
37+
String[] values = filterValue.split(ESCAPED_OR_SEPARATOR);
38+
if (values.length > 0 && isBoolean(values[0])) {
39+
List<Boolean> booleanValues = new ArrayList<Boolean>();
40+
for (int i = 0; i < values.length; i++) {
41+
booleanValues.add(Boolean.valueOf(values[i]));
42+
}
43+
predicate = predicate.and(entity.getBoolean(column.getData()).in(booleanValues));
44+
} else {
45+
predicate.and(getStringExpression(entity, column.getData()).in(values));
46+
}
47+
} else {
48+
// the filter contains only one value, add a 'WHERE .. LIKE' clause
49+
if (isBoolean(filterValue)) {
50+
predicate =
51+
predicate.and(entity.getBoolean(column.getData()).eq(Boolean.valueOf(filterValue)));
52+
} else {
53+
predicate = predicate.and(getStringExpression(entity, column.getData()).lower()
54+
.like(getLikeFilterValue(filterValue), ESCAPE_CHAR));
55+
}
56+
}
57+
}
58+
59+
// check whether a global filter value exists
60+
String globalFilterValue = input.getSearch().getValue();
61+
if (StringUtils.hasText(globalFilterValue)) {
62+
BooleanBuilder matchOneColumnPredicate = new BooleanBuilder();
63+
// add a 'WHERE .. LIKE' clause on each searchable column
64+
for (ColumnParameter column : input.getColumns()) {
65+
if (column.getSearchable()) {
66+
matchOneColumnPredicate =
67+
matchOneColumnPredicate.or(getStringExpression(entity, column.getData()).lower()
68+
.like(getLikeFilterValue(globalFilterValue), ESCAPE_CHAR));
69+
}
70+
}
71+
predicate = predicate.and(matchOneColumnPredicate);
72+
}
73+
return predicate;
74+
}
75+
76+
private static StringExpression getStringExpression(PathBuilder<?> entity, String columnData) {
77+
return Expressions.stringOperation(Ops.STRING_CAST, entity.get(columnData));
78+
}
79+
80+
}

src/main/java/org/springframework/data/jpa/datatables/qrepository/QDataTablesRepositoryImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.springframework.data.jpa.datatables.qrepository;
22

33
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.getPageable;
4-
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.getPredicate;
54

65
import java.io.Serializable;
76
import java.util.List;
@@ -81,8 +80,9 @@ public <R> DataTablesOutput<R> findAll(DataTablesInput input, Predicate addition
8180
}
8281
output.setRecordsTotal(recordsTotal);
8382

84-
Page<T> data = findAll(new BooleanBuilder().and(getPredicate(this.builder, input))
85-
.and(additionalPredicate).and(preFilteringPredicate).getValue(), getPageable(input));
83+
Predicate predicate = PredicateFactory.createPredicate(this.builder, input);
84+
Page<T> data = findAll(new BooleanBuilder().and(predicate).and(additionalPredicate)
85+
.and(preFilteringPredicate).getValue(), getPageable(input));
8686

8787
@SuppressWarnings("unchecked")
8888
List<R> content =

src/main/java/org/springframework/data/jpa/datatables/repository/DataTablesRepositoryImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.springframework.data.jpa.datatables.repository;
22

33
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.getPageable;
4-
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.getSpecification;
54

65
import java.io.Serializable;
76
import java.util.List;
@@ -68,8 +67,9 @@ public <R> DataTablesOutput<R> findAll(DataTablesInput input,
6867
}
6968
output.setRecordsTotal(recordsTotal);
7069

71-
Page<T> data = findAll(Specifications.where(getSpecification(getDomainClass(), input))
72-
.and(additionalSpecification).and(preFilteringSpecification), getPageable(input));
70+
Specification<T> specification = SpecificationFactory.createSpecification(input);
71+
Page<T> data = findAll(Specifications.where(specification).and(additionalSpecification)
72+
.and(preFilteringSpecification), getPageable(input));
7373

7474
@SuppressWarnings("unchecked")
7575
List<R> content =
Lines changed: 7 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
package org.springframework.data.jpa.datatables.repository;
22

33
import java.util.ArrayList;
4-
import java.util.Arrays;
54
import 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-
176
import org.springframework.data.domain.PageRequest;
187
import org.springframework.data.domain.Pageable;
198
import org.springframework.data.domain.Sort;
@@ -22,169 +11,14 @@
2211
import org.springframework.data.jpa.datatables.mapping.DataTablesInput;
2312
import org.springframework.data.jpa.datatables.parameter.ColumnParameter;
2413
import 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

3415
public 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

Comments
 (0)