Skip to content

Commit

Permalink
#50 support isNull in custom operator predicate
Browse files Browse the repository at this point in the history
  • Loading branch information
perplexhub committed Apr 21, 2021
1 parent a869161 commit 3d9d5e0
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 68 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,11 @@ repository.findAll(toPredicate(filter, QUser.user, propertyPathMapper), pageable
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));
```

```java
String rsql = "city=notAssigned=''";
RSQLCustomPredicate<String> customPredicate = new RSQLCustomPredicate<>(new ComparisonOperator("=notAssigned="), String.class, input -> {
return input.getCriteriaBuilder().isNull(input.getRoot().get("city"));
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;

import lombok.Value;
Expand All @@ -13,5 +14,6 @@ public class RSQLCustomPredicateInput {
private CriteriaBuilder criteriaBuilder;
private Path<?> path;
private List<Object> arguments;
private From root;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.github.perplexhub.rsql.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.Id;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class City {

@Id
private Integer id;

private String name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public class User {
@JoinColumn(name = "companyId", referencedColumnName = "id")
private Company company;

@ManyToOne(optional = true)
@JoinColumn(name = "cityId", referencedColumnName = "id", nullable = true)
private City city;

@OneToMany(mappedBy = "id.userId")
private List<UserRole> userRoles;

Expand Down
2 changes: 1 addition & 1 deletion rsql-common/src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=create-drop

spring.jpa.properties.hibernate.hbm2ddl.import_files=import_company.sql,import_user.sql,import_role.sql,import_user_role.sql,import_project.sql
spring.jpa.properties.hibernate.hbm2ddl.import_files=import_company.sql,import_city.sql,import_user.sql,import_role.sql,import_user_role.sql,import_project.sql

logging.level.io.github.perplexhub.rsql=DEBUG
logging.level.org.hibernate.SQL=TRACE
Expand Down
1 change: 1 addition & 0 deletions rsql-common/src/test/resources/import_city.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
insert into city(id, name) values(1, 'Moon');
2 changes: 1 addition & 1 deletion rsql-common/src/test/resources/import_user.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
insert into users(id, name, company_id, create_date, status) values(1, 'January', 1, '2018-01-01', 'STARTED');
insert into users(id, name, company_id, create_date, status, city_id) values(1, 'January', 1, '2018-01-01', 'STARTED', 1);
insert into users(id, name, company_id, create_date, status) values(2, 'February', 1, '2018-02-01', 'STARTED');
insert into users(id, name, company_id, create_date, status) values(3, 'March', 2, '2018-03-01', 'STARTED');
insert into users(id, name, company_id, create_date, status) values(4, 'April', 2, '2018-04-01', 'STARTED');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,70 +48,70 @@ <T> RSQLJPAContext findPropertyPath(String propertyPath, Path startRoot) {

String[] properties = propertyPath.split("\\.");

for (String property : properties) {
String mappedProperty = mapProperty(property, classMetadata.getJavaType());
if (!mappedProperty.equals(property)) {
RSQLJPAContext context = findPropertyPath(mappedProperty, root);
root = context.getPath();
attribute = context.getAttribute();
} else {
if (!hasPropertyName(mappedProperty, classMetadata)) {
if (Modifier.isAbstract(classMetadata.getJavaType().getModifiers())) {
Optional<Class<?>> foundSubClass = (Optional<Class<?>>) new Reflections(classMetadata.getJavaType().getPackage().getName())
.getSubTypesOf(classMetadata.getJavaType())
.stream()
.filter(subType -> hasPropertyName(mappedProperty, getManagedType(subType)))
.findFirst();
if (foundSubClass.isPresent()) {
classMetadata = getManagedType(foundSubClass.get());
root = root instanceof Join ? builder.treat((Join) root, foundSubClass.get()).get(property) : builder.treat((Path) root, foundSubClass.get()).get(property);
attribute = classMetadata.getAttribute(property);
} else {
for (String property : properties) {
String mappedProperty = mapProperty(property, classMetadata.getJavaType());
if (!mappedProperty.equals(property)) {
RSQLJPAContext context = findPropertyPath(mappedProperty, root);
root = context.getPath();
attribute = context.getAttribute();
} else {
if (!hasPropertyName(mappedProperty, classMetadata)) {
if (Modifier.isAbstract(classMetadata.getJavaType().getModifiers())) {
Optional<Class<?>> foundSubClass = (Optional<Class<?>>) new Reflections(classMetadata.getJavaType().getPackage().getName())
.getSubTypesOf(classMetadata.getJavaType())
.stream()
.filter(subType -> hasPropertyName(mappedProperty, getManagedType(subType)))
.findFirst();
if (foundSubClass.isPresent()) {
classMetadata = getManagedType(foundSubClass.get());
root = root instanceof Join ? builder.treat((Join) root, foundSubClass.get()).get(property) : builder.treat((Path) root, foundSubClass.get()).get(property);
attribute = classMetadata.getAttribute(property);
} else {
throw new IllegalArgumentException("Unknown property: " + mappedProperty + " from entity " + classMetadata.getJavaType().getName());
}
} else {
throw new IllegalArgumentException("Unknown property: " + mappedProperty + " from entity " + classMetadata.getJavaType().getName());
}
} else {
if (isAssociationType(mappedProperty, classMetadata)) {
boolean isOneToAssociationType = isOneToOneAssociationType(mappedProperty, classMetadata) || isOneToManyAssociationType(mappedProperty, classMetadata);
Class<?> associationType = findPropertyType(mappedProperty, classMetadata);
type = associationType;
String previousClass = classMetadata.getJavaType().getName();
classMetadata = getManagedType(associationType);
} else {
throw new IllegalArgumentException("Unknown property: " + mappedProperty + " from entity " + classMetadata.getJavaType().getName());
}
} else {
if (isAssociationType(mappedProperty, classMetadata) && !property.equals(propertyPath)) {
boolean isOneToAssociationType = isOneToOneAssociationType(mappedProperty, classMetadata) || isOneToManyAssociationType(mappedProperty, classMetadata);
Class<?> associationType = findPropertyType(mappedProperty, classMetadata);
type = associationType;
String previousClass = classMetadata.getJavaType().getName();
classMetadata = getManagedType(associationType);

String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
log.debug("Create a join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
root = isOneToAssociationType ? joinLeft(keyJoin, root, mappedProperty) : join(keyJoin, root, mappedProperty);
} else if (isElementCollectionType(mappedProperty, classMetadata)) {
String previousClass = classMetadata.getJavaType().getName();
attribute = classMetadata.getAttribute(property);
classMetadata = getManagedElementCollectionType(mappedProperty, classMetadata);
String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
log.debug("Create a join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
root = isOneToAssociationType ? joinLeft(keyJoin, root, mappedProperty) : join(keyJoin, root, mappedProperty);
} else if (isElementCollectionType(mappedProperty, classMetadata)) {
String previousClass = classMetadata.getJavaType().getName();
attribute = classMetadata.getAttribute(property);
classMetadata = getManagedElementCollectionType(mappedProperty, classMetadata);

String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
log.debug("Create a element collection join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
root = join(keyJoin, root, mappedProperty);
} else {
log.debug("Create property path for type [{}] property [{}]", classMetadata.getJavaType().getName(), mappedProperty);
root = root.get(mappedProperty);
String keyJoin = root.getJavaType().getSimpleName().concat(".").concat(mappedProperty);
log.debug("Create a element collection join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin);
root = join(keyJoin, root, mappedProperty);
} else {
log.debug("Create property path for type [{}] property [{}]", classMetadata.getJavaType().getName(), mappedProperty);
root = root.get(mappedProperty);

if (isEmbeddedType(mappedProperty, classMetadata)) {
Class<?> embeddedType = findPropertyType(mappedProperty, classMetadata);
type = embeddedType;
classMetadata = getManagedType(embeddedType);
} else {
attribute = classMetadata.getAttribute(property);
}
}
}
}
}
if (isEmbeddedType(mappedProperty, classMetadata)) {
Class<?> embeddedType = findPropertyType(mappedProperty, classMetadata);
type = embeddedType;
classMetadata = getManagedType(embeddedType);
} else {
attribute = classMetadata.getAttribute(property);
}
}
}
}
}

if (attribute != null) {
accessControl(type, attribute.getName());
}
if (attribute != null) {
accessControl(type, attribute.getName());
}

return RSQLJPAContext.of(root, attribute);
return RSQLJPAContext.of(root, attribute);
}

protected Path<?> join(String keyJoin, Path<?> root, String mappedProperty) {
Expand Down Expand Up @@ -145,6 +145,16 @@ public Predicate visit(ComparisonNode node, From root) {
ComparisonOperator op = node.getOperator();
RSQLJPAContext holder = findPropertyPath(mapPropertyPath(node.getSelector()), root);
Path attrPath = holder.getPath();

if (customPredicates.containsKey(op)) {
RSQLCustomPredicate<?> customPredicate = customPredicates.get(op);
List<Object> arguments = new ArrayList<>();
for (String argument : node.getArguments()) {
arguments.add(convert(argument, customPredicate.getType()));
}
return customPredicate.getConverter().apply(RSQLCustomPredicateInput.of(builder, attrPath, arguments, root));
}

Attribute attribute = holder.getAttribute();
Class type = attribute.getJavaType();
if (attribute.getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION) {
Expand All @@ -156,15 +166,6 @@ public Predicate visit(ComparisonNode node, From root) {
type = RSQLJPASupport.getValueTypeMap().get(type); // if you want to treat Enum as String and apply like search, etc
}

if (customPredicates.containsKey(op)) {
RSQLCustomPredicate<?> customPredicate = customPredicates.get(op);
List<Object> arguments = new ArrayList<>();
for (String argument : node.getArguments()) {
arguments.add(convert(argument, customPredicate.getType()));
}
return customPredicate.getConverter().apply(RSQLCustomPredicateInput.of(builder, attrPath, arguments));
}

if (node.getArguments().size() > 1) {
List<Object> listObject = new ArrayList<>();
for (String argument : node.getArguments()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ public class RSQLJPASupportTest {
@Autowired
private TrunkGroupRepository trunkGroupRepository;

@Test
public final void testCustomPredicateIsNull() {
String rsql = "city=notAssigned=''";
RSQLCustomPredicate<String> customPredicate = new RSQLCustomPredicate<>(new ComparisonOperator("=notAssigned="), String.class, input -> {
return input.getCriteriaBuilder().isNull(input.getRoot().get("city"));
});
List<User> users = userRepository.findAll(toSpecification(rsql, Arrays.asList(customPredicate)));
long count = users.size();
log.info("rsql: {} -> count: {}", rsql, count);
assertThat(rsql, count, is(11l));
}

@Test
public final void testCustomPredicateBetween() {
String rsql = "company.id=between=(2,3)";
Expand Down

0 comments on commit 3d9d5e0

Please sign in to comment.