Skip to content

Commit

Permalink
Add spotless plugin on querydsl-criteria-builder
Browse files Browse the repository at this point in the history
  • Loading branch information
PabloLec committed May 8, 2024
1 parent 54b8cc2 commit 4d62fa7
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 66 deletions.
39 changes: 39 additions & 0 deletions querydsl-criteria-builder/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
<version>0.0.1-SNAPSHOT</version>
<name>backend</name>
<description>backend</description>

<properties>
<java.version>21</java.version>
<spotless.version>2.43.0</spotless.version>
</properties>

<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
Expand All @@ -39,4 +42,40 @@
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>${spotless.version}</version>
<executions>
<execution>
<goals>
<goal>apply</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<java>
<includes>
<include>src/main/java/**/*.java</include>
<include>src/test/java/**/*.java</include>
</includes>
<importOrder />
<removeUnusedImports />
<toggleOffOn/>
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<tabs>true</tabs>
<spacesPerTab>4</spacesPerTab>
</indent>
<palantirJavaFormat/>
</java>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
import com.querydsl.jpa.impl.JPAQuery;
import dev.pablolec.querybuilder.model.SearchCriterion;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class CriteriaQueryBuilder {
Expand All @@ -34,7 +32,8 @@ public JPAQuery<?> buildQuery(List<SearchCriterion> criteria, String entityPathS
return addCriteriaToQuery(criteria, rootEntityPath, query);
}

private JPAQuery<?> addCriteriaToQuery(List<SearchCriterion> criteria, EntityPathBase<?> rootEntityPath, JPAQuery<?> query) {
private JPAQuery<?> addCriteriaToQuery(
List<SearchCriterion> criteria, EntityPathBase<?> rootEntityPath, JPAQuery<?> query) {
criteria.stream()
.map(criterion -> getBooleanExpression(criterion, rootEntityPath))
.forEach(query::where);
Expand All @@ -47,7 +46,8 @@ private BooleanExpression getBooleanExpression(SearchCriterion criterion, Entity
validateSubQueryCriteria(criterion.getSubCriteria());

JPAQuery<?> subQuery = buildSubQuery(rootEntityPath, criterion.getField());
EntityPathBase<?> childEntityPath = entityPathResolver.getEntityPathBase(getFieldPathParts(criterion.getField()).getLast());
EntityPathBase<?> childEntityPath = entityPathResolver.getEntityPathBase(
getFieldPathParts(criterion.getField()).getLast());
addCriteriaToQuery(criterion.getSubCriteria(), childEntityPath, subQuery);

return ExpressionBuilder.buildSubQueryExpression(criterion, subQuery);
Expand Down Expand Up @@ -99,26 +99,21 @@ private EntityPathBase<?> applyJoin(JPAQuery<?> subQuery, EntityPathBase<?> curr
}

private BooleanExpression getJoinCondition(EntityPathBase<?> parentEntityPath, EntityPathBase<?> childEntityPath) {
try {
Class<?> parentEntityClass = parentEntityPath.getType();
for (Field field : parentEntityClass.getDeclaredFields()) {
Class<?> fieldType = field.getType();

if (fieldType.equals(childEntityPath.getType())) {
return getDirectJoinCondition(parentEntityClass, parentEntityPath, childEntityPath, field);
}

if (Iterable.class.isAssignableFrom(fieldType)) {
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
Class<?> collectionType = (Class<?>) parameterizedType.getActualTypeArguments()[0];

if (collectionType.equals(childEntityPath.getType())) {
return getCollectionJoinCondition(parentEntityClass, parentEntityPath, childEntityPath, field);
}
}
Class<?> parentEntityClass = parentEntityPath.getType();

for (Field field : parentEntityClass.getDeclaredFields()) {
PathBuilder<?> parentEntityBuilder = new PathBuilder<>(
parentEntityPath.getType(), parentEntityPath.getMetadata().getName());
PathBuilder<?> childEntityBuilder = new PathBuilder<>(
childEntityPath.getType(), childEntityPath.getMetadata().getName());

if (isDirectJoinApplicable(field, childEntityPath)) {
return buildDirectJoinCondition(parentEntityBuilder, childEntityBuilder, field);
}

if (isCollectionJoinApplicable(field, childEntityPath)) {
return buildCollectionJoinCondition(parentEntityBuilder, childEntityBuilder, field);
}
} catch (Exception e) {
throw new IllegalStateException("Error finding join condition between entities", e);
}

throw new IllegalStateException("No valid join condition found between entities "
Expand All @@ -127,14 +122,30 @@ private BooleanExpression getJoinCondition(EntityPathBase<?> parentEntityPath, E
+ childEntityPath.getType().getSimpleName());
}

private BooleanExpression getDirectJoinCondition(Class<?> parentEntityClass, EntityPathBase<?> parentEntityPath, EntityPathBase<?> childEntityPath, Field field) {
PathBuilder<?> parentEntityBuilder = new PathBuilder<>(parentEntityClass, parentEntityPath.getMetadata().getName());
return parentEntityBuilder.get(field.getName()).get("id").eq(new PathBuilder<>(childEntityPath.getType(), childEntityPath.getMetadata().getName()).get("id"));
private boolean isDirectJoinApplicable(Field field, EntityPathBase<?> childEntityPath) {
return field.getType().equals(childEntityPath.getType());
}

private BooleanExpression getCollectionJoinCondition(Class<?> parentEntityClass, EntityPathBase<?> parentEntityPath, EntityPathBase<?> childEntityPath, Field field) {
PathBuilder<?> parentEntityBuilder = new PathBuilder<>(parentEntityClass, parentEntityPath.getMetadata().getName());
return parentEntityBuilder.getCollection(field.getName(), parentEntityClass).any().get("id").eq(new PathBuilder<>(childEntityPath.getType(), childEntityPath.getMetadata().getName()).get("id"));
private boolean isCollectionJoinApplicable(Field field, EntityPathBase<?> childEntityPath) {
if (Iterable.class.isAssignableFrom(field.getType())) {
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
Class<?> collectionType = (Class<?>) parameterizedType.getActualTypeArguments()[0];
return collectionType.equals(childEntityPath.getType());
}
return false;
}
}

private BooleanExpression buildDirectJoinCondition(
PathBuilder<?> parentEntityBuilder, PathBuilder<?> childEntityBuilder, Field field) {
return parentEntityBuilder.get(field.getName()).get("id").eq(childEntityBuilder.get("id"));
}

private BooleanExpression buildCollectionJoinCondition(
PathBuilder<?> parentEntityBuilder, PathBuilder<?> childEntityBuilder, Field field) {
return parentEntityBuilder
.getCollection(field.getName(), field.getType())
.any()
.get("id")
.eq(childEntityBuilder.get("id"));
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package dev.pablolec.querybuilder;

import lombok.experimental.UtilityClass;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.List;
import lombok.experimental.UtilityClass;

@UtilityClass
class DynamicFieldCaster {
Expand Down Expand Up @@ -53,7 +52,6 @@ private static Field getField(Class<?> type, String fieldName) throws NoSuchFiel
throw new NoSuchFieldException("Field not found: " + fieldName);
}


private static Object castSingleValue(Class<?> fieldType, String value) {
String typeName = fieldType.getCanonicalName();
return switch (typeName) {
Expand All @@ -62,13 +60,16 @@ private static Object castSingleValue(Class<?> fieldType, String value) {
case "java.time.LocalDate" -> LocalDate.parse(value);
case "java.time.LocalDateTime" -> LocalDateTime.parse(value);
case "java.lang.String" -> value;
default ->
throw new IllegalArgumentException("Unsupported field type for dynamic casting: " + fieldType.getSimpleName());
default -> throw new IllegalArgumentException(
"Unsupported field type for dynamic casting: " + fieldType.getSimpleName());
};
}

private static List<?> castCollection(Class<?> fieldType, String value) {
String[] elements = value.replace("[", "").replace("]", "").split(",");
return Arrays.stream(elements).map(element -> castSingleValue(fieldType, element.trim())).toList();

return Arrays.stream(elements)
.map(element -> castSingleValue(fieldType, element.trim()))
.toList();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dev.pablolec.querybuilder;

import com.querydsl.core.types.dsl.EntityPathBase;

import java.util.Collections;
import java.util.Map;

Expand Down Expand Up @@ -39,5 +38,4 @@ public EntityPathBase<?> getEntityPathBase(Class<?> targetClass) {

throw new IllegalStateException("No matching EntityPathBase found for '" + targetClass.getSimpleName() + "'");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,33 @@
import com.querydsl.jpa.impl.JPAQuery;
import dev.pablolec.querybuilder.model.Operator;
import dev.pablolec.querybuilder.model.SearchCriterion;
import lombok.experimental.UtilityClass;

import java.time.temporal.TemporalAccessor;
import java.util.List;
import lombok.experimental.UtilityClass;

@UtilityClass
public class ExpressionBuilder {
public static BooleanExpression buildExpression(SearchCriterion criterion, EntityPathBase<?> rootEntityPath) {
PathBuilder<?> pathBuilder = new PathBuilder<>(rootEntityPath.getType(), rootEntityPath.getMetadata().getName());
PathBuilder<?> pathBuilder = new PathBuilder<>(
rootEntityPath.getType(), rootEntityPath.getMetadata().getName());
Operator operator = Operator.fromString(criterion.getOp());
Object castedValue = DynamicFieldCaster.castValue(pathBuilder.getType(), criterion.getField(), criterion.getValue(), operator.isCollectionOperator());
Object castedValue = DynamicFieldCaster.castValue(
pathBuilder.getType(), criterion.getField(), criterion.getValue(), operator.isCollectionOperator());

return switch (operator) {
case EQ, NE -> handleBasicComparisons(pathBuilder, criterion.getField(), castedValue, operator);
case LIKE, NOT_LIKE -> {
if (!(castedValue instanceof String)) {
throw new IllegalArgumentException(operator + " operator is only valid for String types.");
}
yield operator == Operator.LIKE ?
pathBuilder.getString(criterion.getField()).like((String) castedValue) :
pathBuilder.getString(criterion.getField()).notLike((String) castedValue);
yield operator == Operator.LIKE
? pathBuilder.getString(criterion.getField()).like((String) castedValue)
: pathBuilder.getString(criterion.getField()).notLike((String) castedValue);
}
case GT, LT, GTE, LTE ->
handleComparisonOperators(pathBuilder, criterion.getField(), castedValue, operator);
case IN, NOT_IN ->
handleCollectionExpression(pathBuilder, criterion.getField(), (List<?>) castedValue, operator);
case GT, LT, GTE, LTE -> handleComparisonOperators(
pathBuilder, criterion.getField(), castedValue, operator);
case IN, NOT_IN -> handleCollectionExpression(
pathBuilder, criterion.getField(), (List<?>) castedValue, operator);
default -> throw new IllegalArgumentException("Unsupported operator: " + operator);
};
}
Expand All @@ -43,38 +44,44 @@ public static BooleanExpression buildSubQueryExpression(SearchCriterion criterio
};
}

private static BooleanExpression handleBasicComparisons(PathBuilder<?> entityPath, String fieldName, Object value, Operator operator) {
private static BooleanExpression handleBasicComparisons(
PathBuilder<?> entityPath, String fieldName, Object value, Operator operator) {
return switch (operator) {
case EQ -> entityPath.get(fieldName).eq(value);
case NE -> entityPath.get(fieldName).ne(value);
default -> throw new IllegalStateException("Unexpected operator for basic comparisons: " + operator);
};
}

private static BooleanExpression handleComparisonOperators(PathBuilder<?> entityPath, String fieldName, Object value, Operator operator) {
private static BooleanExpression handleComparisonOperators(
PathBuilder<?> entityPath, String fieldName, Object value, Operator operator) {
if (!(value instanceof Comparable)) {
throw new IllegalArgumentException("Comparison operators are only supported for Comparable types. Field: " + fieldName + ", Value type: " + value.getClass().getSimpleName());
throw new IllegalArgumentException("Comparison operators are only supported for Comparable types. Field: "
+ fieldName + ", Value type: " + value.getClass().getSimpleName());
}

return switch (value) {
case Number numberValue -> compareUsingGenericNumberPath(entityPath, fieldName, numberValue, operator);
case TemporalAccessor temporalValue ->
compareUsingGenericTemporalPath(entityPath, fieldName, temporalValue, operator);
default ->
throw new IllegalArgumentException("Comparison operators are not supported for the type of " + fieldName + ": " + value.getClass().getSimpleName());
case TemporalAccessor temporalValue -> compareUsingGenericTemporalPath(
entityPath, fieldName, temporalValue, operator);
default -> throw new IllegalArgumentException("Comparison operators are not supported for the type of "
+ fieldName + ": " + value.getClass().getSimpleName());
};
}

private static BooleanExpression handleCollectionExpression(PathBuilder<?> entityPath, String fieldName, List<?> value, Operator operator) {
private static BooleanExpression handleCollectionExpression(
PathBuilder<?> entityPath, String fieldName, List<?> value, Operator operator) {
return switch (operator) {
case IN -> entityPath.get(fieldName).in(value);
case NOT_IN -> entityPath.get(fieldName).notIn(value);
default -> throw new IllegalStateException("Unexpected collection operator: " + operator);
};
}

private static <N extends Number & Comparable<N>> BooleanExpression compareUsingGenericNumberPath(PathBuilder<?> entityPath, String fieldName, Number numberValue, Operator operator) {
@SuppressWarnings("unchecked") Class<N> type = (Class<N>) numberValue.getClass();
private static <N extends Number & Comparable<N>> BooleanExpression compareUsingGenericNumberPath(
PathBuilder<?> entityPath, String fieldName, Number numberValue, Operator operator) {
@SuppressWarnings("unchecked")
Class<N> type = (Class<N>) numberValue.getClass();
NumberPath<N> path = entityPath.getNumber(fieldName, type);
N castedValue = type.cast(numberValue);

Expand All @@ -87,8 +94,10 @@ private static <N extends Number & Comparable<N>> BooleanExpression compareUsing
};
}

private static <T extends Comparable<T>> BooleanExpression compareUsingGenericTemporalPath(PathBuilder<?> entityPath, String fieldName, TemporalAccessor temporalValue, Operator operator) {
@SuppressWarnings("unchecked") Class<T> type = (Class<T>) temporalValue.getClass();
private static <T extends Comparable<T>> BooleanExpression compareUsingGenericTemporalPath(
PathBuilder<?> entityPath, String fieldName, TemporalAccessor temporalValue, Operator operator) {
@SuppressWarnings("unchecked")
Class<T> type = (Class<T>) temporalValue.getClass();
TemporalExpression<T> path = entityPath.getDateTime(fieldName, type);
T castedValue = type.cast(temporalValue);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package dev.pablolec.querybuilder.model;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;

@Data
@AllArgsConstructor
public class SearchCriterion {
Expand All @@ -29,4 +28,4 @@ public SearchCriterion(String field, String op, List<SearchCriterion> subCriteri
this.subQuery = true;
this.subCriteria = subCriteria;
}
}
}

0 comments on commit 4d62fa7

Please sign in to comment.