0

I have a scenario where I need to add Criteria to perform search and filter in Spring using mongoTemplate.

Scenario:

Lets say I have Student, Course and PotentialStudent. and I have to define only certain fields to be used for search and filter purpose. For PotentialStudent, it contains both Student and Course information that is collected before all required information is gathered to be filled to Student and Course.

Search Fields are the fields to be used for searching either of the fields. For example: get values matching in either courseName or courseType in Course.

Filter is to be used to filter specific fields for matching multiple values and the values to be filtered on field is set on FilterParams. Meaning, if I get values in FilterParams.studentType then for PotentialStudent I should add Criteria to search inside PotentialStudent's student.type for list of values whereas if for Student add Criteria to search in Student's type.

public abstract class Model {
    @Id
    protected String id;
    @CreatedDate
    protected Date createdDateTime;
    @LastModifiedDate
    protected Date modifiedDateTime;


    protected abstract List<String> searchFields();

    protected abstract Map<String, String> filterFields();
}

@Getter
@Setter
@Document("student")
public class Student extends Model {

    private String firstName;
    private String lastName;
    private String address;
    private StudentType type;

    @Override
    protected List<String> searchFields() {
        return Lists.newArrayList("firstName","lastName","address");
    }

    @Override
    protected Map<String, String> filterFields() {
        Map<String, String> filterMap = Maps.newHashMap();
        filterMap.put("studentType", "type");
        return filterMap;
    }
}

@Getter
@Setter
@Document("course")
public class Course extends Model {

    private String courseName;
    private String courseType;
    private int duration;
    private Difficulty difficulty;

    @Override
    protected List<String> searchFields() {
        return Lists.newArrayList("courseName","courseType");
    }

    @Override
    protected Map<String, String> filterFields() {
        Map<String, String> filterMap = Maps.newHashMap();
        filterMap.put("courseDifficulty", "difficulty");
        return filterMap;
    }
}

@Getter
@Setter
@Document("course")
public class PotentialStudent extends Model {

    private Student student;
    private Course course;

    @Override
    protected List<String> searchFields() {
        return Lists.newArrayList("student.firstName","student.lastName","course.courseName");
    }

    @Override
    protected Map<String, String> filterFields() {
            Map<String, String> filterMap = Maps.newHashMap();
            filterMap.put("studentType", "student.type");
            filterMap.put("courseDifficulty", "course.difficulty");

            return filterMap;
        }
    }
}

public class FilterParams {

    private List<StudentType> studentTypes;
    private List<Difficulty> difficulties;
}

public class PageData<T extends Model> {

    public void setPageRecords(List<T> pageRecords) {
        this.pageRecords = pageRecords;
    }

    private List<T> pageRecords;
}

//Generic Search Filter Implementation Class
public class GenericSearchFilter {
    public <T extends Model> PageData getRecordsWithPageSearchFilter(Integer page, Integer size, String sortName, String sortOrder, String value, FilterParams filterParams, Class<T> ormClass) {


        PageRequestBuilder pageRequestBuilder = new PageRequestBuilder();
        Pageable pageable = pageRequestBuilder.getPageRequest(page, size, sortName, sortOrder);

        Query mongoQuery = new Query().with(pageable);


        //add Criteria for the domain specified search fields 
        Criteria searchCriteria = searchCriteria(value, ormClass);
        if (searchCriteria != null) {
            mongoQuery.addCriteria(searchCriteria);
        }



        //Handle Filter
        query.addCriteria(Criteria.where(filterFields().get("studentType")).in(filterParams.getStudentTypes()));
        query.addCriteria(Criteria.where(filterFields().get("courseDifficulty")).in(filterParams.getDifficulty()));

        List<T> records = mongoTemplate.find(mongoQuery, ormClass);

        PageData pageData = new PageData();
        pageData.setPageRecords(records);


        return pageData;
    }

    private <T extends BaseDocument> Criteria searchCriteria(String value, Class<T> ormClass)  {
        try {

            Criteria orCriteria = new Criteria();
            if (StringUtils.isNotBlank(value)) {
                BaseDocument document = ormClass.getDeclaredConstructor().newInstance();
                Method method = ormClass.getDeclaredMethod("searchFields");
                List<String> records = (List<String>) method.invoke(document, null);
                Criteria[] orCriteriaArray = records.stream().map(s -> Criteria.where(s).regex(value, "i")).toArray(Criteria[]::new);
                orCriteria.orOperator(orCriteriaArray);
            }

            return orCriteria;

        } catch (Exception e) {
            log.error(e.getMessage());
        }

        return null;

    }
}

Given this scenario, my question is how to handle filter cases in better and dynamic way and how to implement a Global search if needed to search in all Document types for specified fields on each types.

Sujal
  • 671
  • 1
  • 16
  • 34
  • Are "text indexes" what you are looking for? You annotate each field with @TextIndexed from spring-data-mongodb, and search with this query: {$text:{$search:?1}} – Eduard Feb 11 '20 at 08:19
  • @Eduard Not exactly, I need to search matching any words or letters as well on any of the defined fields and I am using Criteria with regex for that to get the desired fields to be search for and matching the value inserted in search box to any matches on the defined fields. – Sujal Feb 11 '20 at 10:40
  • @Eduard. Do you know any ideas on implementing Views on spring-data-mongodb to be able to define search across multiple documents? – Sujal Feb 13 '20 at 08:14
  • See https://stackoverflow.com/questions/6502541/mongodb-query-multiple-collections-at-once – Eduard Feb 13 '20 at 09:50

0 Answers0