Search Framework Idea – Java

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

SearchFramework

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.
Advertisements

“Instanceof” is it bad?

Once an interviwer asked me – we know using “instanceof” is bad, can we avoid using it with the help of some pattern (give the name)?

In my 9 years of java programming career I don’t remember that I have used instanceof apart from overriding “equals” method. So first of all this question that “can we avoid its usage with the help of some pattern” made me confuse and I was not able to answer it, later on after doing some reading and trying out some examples I came to the conclusion that its not a right question. Design consideration to avoid “instanceof” in your code totally depend on the problem that you are solving or the requirement you are implementing. Let me explain in detail.

  1. Is using “instanceof” is bad: Can you avoid its usage when overriding equals method in your java bean? answer is NO. In a situation similar to equals method you cannot avoid its usage, but so far I have not seen any such situation, so presently I think we can avoid its usage apart from equals method, and there is no silver bullet pattern.
  2. Please read this artical – “http://butunclebob.com/ArticleS.UncleBob.VisitorVersusInstanceOf”, the solution in this article is done using visitor pattern with reflection to avoid “instanceof” usage. What I feel about this solution is that there is no need of visitor and reflection in the solution, however there could be a situation where visitor will help, and my intent here is to show that there is no silver bullet pattern to avoid “instanceof”. The problem mentioned in the above article is – We have courses and we want to generate report about each course, so separate classes are created for courses and course report generators (to achieve separation of concern). The solution provided in the article is trying to identify report generator based on course and then call methods on that report generator to generate report, here the use of reflection with visitor can be easily avoided if we shift data part in course classes (course title, duration, sysRequired, etc) and report generation behaviour in generic report generator or specific report generator. Lets look at generic report generator.

    GenericReportGenerator {
    Course course;
    generateStandardReport();
    buildTitle() {
    course.getTitle()
    // use course title to generate report title.
    }
    buildHeader();
    buildBody();
    buildFooter();
    }
    You can see with generic report generator there is no need to check instanceof courses or course report generator, also with the help of builder pattern you can change report structure as required (ex: building report by using only buildTitle and buildBody).
    Now lets see specific report generator, we can have JavaReportGenerator and AOODReportGenerator, now in solution every time we create a specific report generator we register it (think of Map having course as key and course report generator as value), further we can use factory to return course report generator by providing course to it.
    This way we can clearly avoid instanceof and reflection usage as defined in the article above, rather you can see in many situations you can avoid instanceof and reflection using appropriate patterns. Some people think that in case you want to call specific method which was introduced by the sub-class/sub-implementor* needs type checking using “instanceof” (however I think this is wrong as new implementation should go in base class, and sub-classes can avoid this implementation if they want to), but this can be avoided using generics if you are using jdk 5 or above or by using specific report generator implementation.

    Some people think “instanceof” introduce lot of if else statement and to avoid that – visitor pattern will help, well I think its wrong, as stated above – refactoring of such code depends on particular implementation, factory, state, builder etc, any pattern can be used depending on the situation. you can even use Predicate (high level pattern**) – please see http://www.infoq.com/presentations/3-Patterns-Cleaner-Code

* in case of interface where you don’t want to break existing implementation by introducing new method in interface.
** pattern mix of several pattern