Introduction
Writing code to implement search in application is a repetitive task, and need understanding of query language provided by underlying search engine. Search framework reduces the development effort and hides implementation complexity by leveraging good Object Oriented principles. It helps to make your design extensible and maintainable.
The solution built using following design patterns
- Builder
- Factory
- Template
- Strategy
Architecture
Core Components
It consist of interfaces defining contract for searcher, and beans defining search request object format.
Code Sample
public interface Searcher {
T search(SearchCriteria criteria);
}
public class SearchCriteria {
private int groupCondition;
private List searchFilters;
}
public class SearchFilter {
private String fieldName;
private String fieldValue;
private int filterCondition;
}
Provider Integration
In this layer we do provider specific implementation, such that it is implemented once and reused.
Sample for JPA Searcher
public abstract class JPASearcher implements Searcher {
@Autowired
private PredicateBuilder predicateBuilder;
protected Specification getSpecification(final SearchCriteria searchCriteria) {
return new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery,
CriteriaBuilder criteriaBuilder) {
criteriaQuery.distinct(true);
return predicateBuilder.build(searchCriteria, criteriaBuilder, root);
}
};
}
JPA Predicate builder
@Component
public class PredicateBuilder {
@Autowired
private PredicateConditionBuilderFactory conditionBuilderFactory;
public Predicate build(SearchCriteria criteria, CriteriaBuilder cb, Root root) {
…
for (SearchFilter searchFilter : criteria.getSearchFilters()) {
…
}
if (criteria.getGroupCondition() == GroupCondition.AND) {
predicate = cb.and(predicates.toArray(new Predicate[predicates.size()]));
} else {
predicate = cb.or(predicates.toArray(new Predicate[predicates.size()]));
}
return predicate;
}
}
JPA Condition Builder
public interface PredicateConditionBuilder { Predicate build(SearchFilter filter, CriteriaBuilder criteriaBuilder, Root root, final DistinctJoinHolder joinHolder); }
Sample Implementation
public class IsNotNullBuilder implements PredicateConditionBuilder { private final ConditionBuilderHelper builderHelper = ConditionBuilderHelper.INSTANCE; @Override public Predicate build(SearchFilter filter, CriteriaBuilder criteriaBuilder, Root root, final DistinctJoinHolder joinHolder) { String fieldName = filter.getField().getSearchFieldName(); GroupCondition groupCondition = filter.getSearchCriteria().getGroupCondition(); Path path = builderHelper.getPath(fieldName, root, groupCondition, joinHolder); return criteriaBuilder.isNotNull(path); } }
JPA Condition Builder Factory
@Component
public class PredicateConditionBuilderFactory {
private Map builders;
@PostConstruct
protected void init() {
builders = new HashMap();
builders.put(FilterCondition.EQUAL, new EqualBuilder());
builders.put(FilterCondition.NOT_EQUAL, new NotEqualBuilder());
builders.put(FilterCondition.GT, new GreaterThanBuilder());
builders.put(FilterCondition.LT, new LessThanBuilder());
builders.put(FilterCondition.EQ_GT, new GreaterThanEqualBuilder());
builders.put(FilterCondition.EQ_LT, new LessThanEqualBuilder());
builders.put(FilterCondition.BETWEEN, new BetweenBuilder());
builders.put(FilterCondition.LIKE, new LikeBuilder());
builders.put(FilterCondition.IN, new InBuilder());
builders.put(FilterCondition.IS_NULL, new IsNullBuilder());
builders.put(FilterCondition.IS_NOT_NULL, new IsNotNullBuilder());
}
public PredicateConditionBuilder getBuilder(FilterCondition filterCondition) {
return builders.get(filterCondition);
}
}
Search Configuration
It is not used directly by core framework, but needed by various search utilities – for example search criteria builder utility may want to get list of fields supported in application’s basic search, or may be for validation configuration.
Search Utilities
This layer consist of various search supporting utilities –
- Search criteria builder & validator.
- Search filter builder & validator.
Simple JPA Searcher implementation (Search Services)
@Component
public class EventSearcher extends JPASearcher {
@Autowired
private EventRepository repository;
@Override
public List search(SearchCriteria criteria) {
return repository.findAll(getSpecification(criteria));
}
}
All you have to do is
- Extend JPASearcher and provide entity to search.
- Provide jpa repository for entity.
- Call search with search criteria.
Good logical presentation