Skip to content

Instantly share code, notes, and snippets.

@yurikilian
Created February 15, 2025 15:20
Show Gist options
  • Save yurikilian/36e4ab8cd6c8d0940c153f9a8d744528 to your computer and use it in GitHub Desktop.
Save yurikilian/36e4ab8cd6c8d0940c153f9a8d744528 to your computer and use it in GitHub Desktop.
Queyr filter DSL
package io.lawtrackr.customer.model;
import static io.lawtrackr.database.filter.model.SearchFilterOperation.BETWEEN;
import static io.lawtrackr.database.filter.model.SearchFilterOperation.EQ;
import static io.lawtrackr.pagination.sort.SortableFields.Field.name;
import java.util.List;
import java.util.Set;
import io.lawtrackr.customer.collection.Customer.CustomerStatus;
import io.lawtrackr.database.filter.model.FilterableDto;
import io.lawtrackr.database.filter.model.LocalDateTimeRangeFilter;
import io.lawtrackr.database.filter.model.SearchFilter;
import io.lawtrackr.pagination.RequestDto;
import io.lawtrackr.pagination.sort.SortableFields;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@SuperBuilder
public class CustomerSearchDto
extends RequestDto implements FilterableDto {
private List<CustomerStatus> status;
private List<String> country;
private LocalDateTimeRangeFilter createdAt;
private String identification;
private String sortBy;
@Override
public String getSortBy() {
if (sortBy == null) {
return "id.asc";
}
return sortBy;
}
@Override
public SortableFields getSortableFields() {
return SortableFields
.of(name("createdAt").withPath("createdDate"))
.and(name("name").withPath("name"))
.and(name("id").withPath("id"))
.and(name("country").withPath("addresses.country"))
.and(name("identification").withPath("identifications.value"))
.withDefaultSortBy("id.desc")
.get();
}
@Override
public Set<SearchFilter> getSearchFilters() {
return Set.of(
new SearchFilter("status", EQ, this.status),
new SearchFilter("addresses.country", EQ, this.country),
new SearchFilter("createdDate", BETWEEN, this.createdAt),
new SearchFilter("identifications.value", EQ, this.identification)
);
}
}
package io.project.database.filter;
import java.util.ArrayList;
import java.util.Collection;
import javax.annotation.Nullable;
import io.lawtrackr.database.filter.model.FilterableDto;
import io.lawtrackr.database.filter.model.LocalDateTimeRangeFilter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;
@Component
public class FilterParser {
public @Nullable Query resolve(final FilterableDto filterable) {
final var searchFilters = filterable.getSearchFilters();
if (searchFilters.isEmpty()) {
return null;
}
final var criteriaList = new ArrayList<Criteria>();
for (var searchFilter : searchFilters.stream().filter(searchFilter -> searchFilter.value() != null).toList()) {
final var value = searchFilter.value();
switch (searchFilter.operation()) {
case EQ -> {
switch (value) {
case Collection<?> collection -> criteriaList.add(Criteria.where(searchFilter.path()).in(collection));
default -> criteriaList.add(Criteria.where(searchFilter.path()).is(value));
}
}
case BETWEEN -> {
switch (value) {
case LocalDateTimeRangeFilter range -> {
criteriaList.add(Criteria.where(searchFilter.path()).lt(range.getEnd()));
criteriaList.add(Criteria.where(searchFilter.path()).gte(range.getStart()));
}
default -> throw new IllegalStateException("Illegal range filter type: " + value.getClass().getName());
}
}
}
}
if (criteriaList.isEmpty()) {
return null;
}
return new Query().addCriteria(new Criteria().andOperator(criteriaList.toArray(new Criteria[0])));
}
}
package io.project.database.filter;
import io.lawtrackr.database.IdDocument;
import io.lawtrackr.database.filter.model.FilterableDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface QueryableByFilter<T extends IdDocument<ID>, ID> {
Page<T> findAll(FilterableDto filterableDto, Pageable pageable);
}
package io.project.database.filter;
import java.util.List;
import io.project.database.IdDocument;
import io.project.database.filter.model.FilterableDto;
import lombok.Setter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import org.springframework.data.support.PageableExecutionUtils;
public class QueryableByFilterBaseClass<T extends IdDocument<ID>, ID> extends SimpleMongoRepository<T, ID> implements QueryableByFilter<T, ID> {
private final MongoOperations mongoOperations;
private final MongoEntityInformation<T, ID> metadata;
@Setter
private FilterParser filterParser;
public QueryableByFilterBaseClass(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
super(metadata, mongoOperations);
this.mongoOperations = mongoOperations;
this.metadata = metadata;
}
@Override
public Page<T> findAll(FilterableDto filterableDto, Pageable pageable) {
final Query resolvedQuery = filterParser.resolve(filterableDto);
if (resolvedQuery == null) {
return findAll(pageable);
}
final var query = resolvedQuery.collation(metadata.getCollation()).with(pageable);
final Class<T> javaType = metadata.getJavaType();
final List<T> list = mongoOperations.find(query, javaType, metadata.getCollectionName());
return PageableExecutionUtils.getPage(list, pageable, () -> mongoOperations
.count(Query.of(query).limit(-1).skip(-1), javaType, metadata.getCollectionName()));
}
}
package io.project.database.filter;
import lombok.NonNull;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.repository.core.RepositoryInformation;
public class SearchFilterRepositoryFactory extends MongoRepositoryFactory {
private final FilterParser searchQueryParser;
public SearchFilterRepositoryFactory(MongoOperations mongoOperations, FilterParser searchQueryParser) {
super(mongoOperations);
this.searchQueryParser = searchQueryParser;
}
@Override
protected @NonNull Object getTargetRepository(@NonNull RepositoryInformation information) {
final Object targetRepository = super.getTargetRepository(information);
if (targetRepository instanceof QueryableByFilter<?, ?>) {
((QueryableByFilterBaseClass<?, ?>) targetRepository).setFilterParser(searchQueryParser);
}
return targetRepository;
}
}
package io.project.database.filter;
import java.io.Serializable;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.lang.Nullable;
public class SearchFilterRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends MongoRepositoryFactoryBean<T, S, ID> {
private @Nullable FilterParser parser;
public SearchFilterRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected @NonNull RepositoryFactorySupport getFactoryInstance(@NonNull final MongoOperations operations) {
return new SearchFilterRepositoryFactory(operations, parser);
}
@Autowired
public void setParser(@Nullable FilterParser parser) {
this.parser = parser;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment