Skip to content

Commit

Permalink
Standardize and improve search functionality (#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
tgianos committed Mar 7, 2017
1 parent aba7ab6 commit 008b8bd
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ public static Specification<ApplicationEntity> find(
return (final Root<ApplicationEntity> root, final CriteriaQuery<?> cq, final CriteriaBuilder cb) -> {
final List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(name)) {
predicates.add(cb.equal(root.get(ApplicationEntity_.name), name));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(ApplicationEntity_.name), name)
);
}
if (StringUtils.isNotBlank(user)) {
predicates.add(cb.equal(root.get(ApplicationEntity_.user), user));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(ApplicationEntity_.user), user)
);
}
if (statuses != null && !statuses.isEmpty()) {
final List<Predicate> orPredicates =
Expand All @@ -86,7 +90,9 @@ public static Specification<ApplicationEntity> find(
);
}
if (StringUtils.isNotBlank(type)) {
predicates.add(cb.equal(root.get(ApplicationEntity_.type), type));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(ApplicationEntity_.type), type)
);
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ public static Specification<ClusterEntity> find(
return (final Root<ClusterEntity> root, final CriteriaQuery<?> cq, final CriteriaBuilder cb) -> {
final List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(name)) {
predicates.add(cb.like(root.get(ClusterEntity_.name), name));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(ClusterEntity_.name), name)
);
}
if (minUpdateTime != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get(ClusterEntity_.updated), minUpdateTime));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ public static Specification<CommandEntity> find(
return (final Root<CommandEntity> root, final CriteriaQuery<?> cq, final CriteriaBuilder cb) -> {
final List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(name)) {
predicates.add(cb.equal(root.get(CommandEntity_.name), name));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(CommandEntity_.name), name)
);
}
if (StringUtils.isNotBlank(user)) {
predicates.add(cb.equal(root.get(CommandEntity_.user), user));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(CommandEntity_.user), user)
);
}
if (statuses != null && !statuses.isEmpty()) {
final List<Predicate> orPredicates =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ public static Predicate getFindPredicate(
) {
final List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(id)) {
predicates.add(cb.like(root.get(JobEntity_.id), id));
predicates.add(JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(JobEntity_.id), id));
}
if (StringUtils.isNotBlank(name)) {
predicates.add(cb.like(root.get(JobEntity_.name), name));
predicates.add(JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(JobEntity_.name), name));
}
if (StringUtils.isNotBlank(user)) {
predicates.add(cb.equal(root.get(JobEntity_.user), user));
predicates.add(JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(JobEntity_.user), user));
}
if (statuses != null && !statuses.isEmpty()) {
final List<Predicate> orPredicates =
Expand All @@ -107,13 +107,17 @@ public static Predicate getFindPredicate(
predicates.add(cb.equal(root.get(JobEntity_.cluster), cluster));
}
if (StringUtils.isNotBlank(clusterName)) {
predicates.add(cb.equal(root.get(JobEntity_.clusterName), clusterName));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(JobEntity_.clusterName), clusterName)
);
}
if (command != null) {
predicates.add(cb.equal(root.get(JobEntity_.command), command));
}
if (StringUtils.isNotBlank(commandName)) {
predicates.add(cb.equal(root.get(JobEntity_.commandName), commandName));
predicates.add(
JpaSpecificationUtils.getStringLikeOrEqualPredicate(cb, root.get(JobEntity_.commandName), commandName)
);
}
if (minStarted != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get(JobEntity_.started), minStarted));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import com.netflix.genie.core.jpa.entities.CommonFieldsEntity;
import org.apache.commons.lang3.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.validation.constraints.NotNull;
import java.util.Set;

Expand All @@ -36,6 +39,26 @@ public final class JpaSpecificationUtils {
protected JpaSpecificationUtils() {
}

/**
* Create either an equals or like predicate based on the presence of the '%' character in the search value.
*
* @param cb The criteria builder to use for predicate creation
* @param expression The expression of the field the predicate is acting on
* @param value The value to compare the field to
* @return A LIKE predicate if the value contains a '%' otherwise an EQUAL predicate
*/
public static Predicate getStringLikeOrEqualPredicate(
@NotNull final CriteriaBuilder cb,
@NotNull final Expression<String> expression,
@NotNull final String value
) {
if (StringUtils.contains(value, PERCENT)) {
return cb.like(expression, value);
} else {
return cb.equal(expression, value);
}
}

/**
* Get the sorted like statement for tags used in specification queries.
*
Expand All @@ -45,15 +68,15 @@ protected JpaSpecificationUtils() {
public static String getTagLikeString(@NotNull final Set<String> tags) {
final StringBuilder builder = new StringBuilder();
tags.stream()
.filter(StringUtils::isNotBlank)
.sorted(String.CASE_INSENSITIVE_ORDER)
.forEach(
tag -> builder
.append(PERCENT)
.append(CommonFieldsEntity.TAG_DELIMITER)
.append(tag)
.append(CommonFieldsEntity.TAG_DELIMITER)
);
.filter(StringUtils::isNotBlank)
.sorted(String.CASE_INSENSITIVE_ORDER)
.forEach(
tag -> builder
.append(PERCENT)
.append(CommonFieldsEntity.TAG_DELIMITER)
.append(tag)
.append(CommonFieldsEntity.TAG_DELIMITER)
);
return builder.append(PERCENT).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/
package com.netflix.genie.core.jpa.specifications;

import com.netflix.genie.test.categories.UnitTest;
import com.netflix.genie.common.dto.ApplicationStatus;
import com.netflix.genie.core.jpa.entities.ApplicationEntity;
import com.netflix.genie.core.jpa.entities.ApplicationEntity_;
import com.netflix.genie.test.categories.UnitTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -78,32 +78,38 @@ public void setup() {

final Path<String> namePath = (Path<String>) Mockito.mock(Path.class);
final Predicate equalNamePredicate = Mockito.mock(Predicate.class);
final Predicate likeNamePredicate = Mockito.mock(Predicate.class);
Mockito.when(this.root.get(ApplicationEntity_.name)).thenReturn(namePath);
Mockito.when(this.cb.equal(namePath, NAME)).thenReturn(equalNamePredicate);
Mockito.when(this.cb.like(namePath, NAME)).thenReturn(likeNamePredicate);

final Path<String> userNamePath = (Path<String>) Mockito.mock(Path.class);
final Predicate equalUserNamePredicate = Mockito.mock(Predicate.class);
final Predicate likeUserNamePredicate = Mockito.mock(Predicate.class);
Mockito.when(this.root.get(ApplicationEntity_.user)).thenReturn(userNamePath);
Mockito.when(this.cb.equal(userNamePath, USER_NAME)).thenReturn(equalUserNamePredicate);
Mockito.when(this.cb.like(userNamePath, USER_NAME)).thenReturn(likeUserNamePredicate);

final Path<ApplicationStatus> statusPath = (Path<ApplicationStatus>) Mockito.mock(Path.class);
final Predicate equalStatusPredicate = Mockito.mock(Predicate.class);
Mockito.when(this.root.get(ApplicationEntity_.status)).thenReturn(statusPath);
Mockito.when(this.cb.equal(Mockito.eq(statusPath), Mockito.any(ApplicationStatus.class)))
.thenReturn(equalStatusPredicate);
.thenReturn(equalStatusPredicate);

final Path<String> tagPath = (Path<String>) Mockito.mock(Path.class);
final Predicate likeTagPredicate = Mockito.mock(Predicate.class);
Mockito.when(this.root.get(ApplicationEntity_.tags)).thenReturn(tagPath);
Mockito.when(this.cb.like(Mockito.eq(tagPath), Mockito.any(String.class)))
.thenReturn(likeTagPredicate);
.thenReturn(likeTagPredicate);

this.tagLikeStatement = JpaSpecificationUtils.getTagLikeString(TAGS);

final Path<String> typePath = (Path<String>) Mockito.mock(Path.class);
final Predicate typePredicate = Mockito.mock(Predicate.class);
final Predicate equalTypePredicate = Mockito.mock(Predicate.class);
final Predicate likeTypePredicate = Mockito.mock(Predicate.class);
Mockito.when(this.root.get(ApplicationEntity_.type)).thenReturn(typePath);
Mockito.when(this.cb.equal(typePath, TYPE)).thenReturn(typePredicate);
Mockito.when(this.cb.equal(typePath, TYPE)).thenReturn(equalTypePredicate);
Mockito.when(this.cb.like(typePath, TYPE)).thenReturn(likeTypePredicate);
}

/**
Expand All @@ -123,6 +129,27 @@ public void testFindAll() {
Mockito.verify(this.cb, Mockito.times(1)).equal(this.root.get(ApplicationEntity_.type), TYPE);
}

/**
* Test the find specification.
*/
@Test
public void testFindAllLike() {
final String newName = NAME + "%";
final String newUser = USER_NAME + "%";
final String newType = TYPE + "%";
final Specification<ApplicationEntity> spec
= JpaApplicationSpecs.find(newName, newUser, STATUSES, TAGS, newType);

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1)).like(this.root.get(ApplicationEntity_.name), newName);
Mockito.verify(this.cb, Mockito.times(1)).like(this.root.get(ApplicationEntity_.user), newUser);
for (final ApplicationStatus status : STATUSES) {
Mockito.verify(this.cb, Mockito.times(1)).equal(this.root.get(ApplicationEntity_.status), status);
}
Mockito.verify(this.cb, Mockito.times(1)).like(this.root.get(ApplicationEntity_.tags), this.tagLikeStatement);
Mockito.verify(this.cb, Mockito.times(1)).like(this.root.get(ApplicationEntity_.type), newType);
}

/**
* Test the find specification.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ public void setup() {

final Path<String> clusterNamePath = (Path<String>) Mockito.mock(Path.class);
final Predicate likeNamePredicate = Mockito.mock(Predicate.class);
final Predicate equalNamePredicate = Mockito.mock(Predicate.class);
Mockito.when(this.root.get(ClusterEntity_.name)).thenReturn(clusterNamePath);
Mockito.when(this.cb.like(clusterNamePath, NAME))
.thenReturn(likeNamePredicate);
Mockito.when(this.cb.like(clusterNamePath, NAME)).thenReturn(likeNamePredicate);
Mockito.when(this.cb.equal(clusterNamePath, NAME)).thenReturn(equalNamePredicate);

final Path<Date> minUpdatePath = (Path<Date>) Mockito.mock(Path.class);
final Predicate greaterThanOrEqualToPredicate = Mockito.mock(Predicate.class);
Expand Down Expand Up @@ -161,7 +162,36 @@ public void testFindAll() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1)).lessThan(this.root.get(ClusterEntity_.updated), MAX_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.tags), this.tagLikeStatement);
for (final ClusterStatus status : STATUSES) {
Mockito.verify(this.cb, Mockito.times(1))
.equal(this.root.get(ClusterEntity_.status), status);
}
}

/**
* Test the find specification.
*/
@Test
public void testFindAllLike() {
final String newName = NAME + "%";
final Specification<ClusterEntity> spec = JpaClusterSpecs
.find(
newName,
STATUSES,
TAGS,
MIN_UPDATE_TIME,
MAX_UPDATE_TIME
);

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), newName);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1)).lessThan(this.root.get(ClusterEntity_.updated), MAX_UPDATE_TIME);
Expand Down Expand Up @@ -190,6 +220,8 @@ public void testFindNoName() {
spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.never())
.like(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.never())
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1)).lessThan(this.root.get(ClusterEntity_.updated), MAX_UPDATE_TIME);
Expand Down Expand Up @@ -217,7 +249,7 @@ public void testFindNoStatuses() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1)).lessThan(
Expand Down Expand Up @@ -246,7 +278,7 @@ public void testFindEmptyStatuses() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1))
Expand Down Expand Up @@ -275,7 +307,7 @@ public void testFindNoTags() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1))
Expand Down Expand Up @@ -304,7 +336,7 @@ public void testFindNoMinTime() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.never())
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.times(1))
Expand Down Expand Up @@ -333,7 +365,7 @@ public void testFindNoMax() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.never())
Expand Down Expand Up @@ -363,7 +395,7 @@ public void testFindEmptyTag() {

spec.toPredicate(this.root, this.cq, this.cb);
Mockito.verify(this.cb, Mockito.times(1))
.like(this.root.get(ClusterEntity_.name), NAME);
.equal(this.root.get(ClusterEntity_.name), NAME);
Mockito.verify(this.cb, Mockito.times(1))
.greaterThanOrEqualTo(this.root.get(ClusterEntity_.updated), MIN_UPDATE_TIME);
Mockito.verify(this.cb, Mockito.never())
Expand Down
Loading

0 comments on commit 008b8bd

Please sign in to comment.