All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kiwiproject.spring.data.KiwiSpringMongoQueries Maven / Gradle / Ivy

Go to download

Kiwi is a utility library. We really like Google's Guava, and also use Apache Commons. But if they don't have something we need, and we think it is useful, this is where we put it.

There is a newer version: 4.5.2
Show newest version
package org.kiwiproject.spring.data;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.kiwiproject.base.KiwiStrings;
import org.kiwiproject.util.function.KiwiBiConsumers;
import org.springframework.data.domain.Page;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Date;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * Static utilities for performing MongoDB queries using Spring Data.
 */
@UtilityClass
@Slf4j
public class KiwiSpringMongoQueries {

    private static final String ANY_STRING = ".*";
    private static final String CASE_INSENSITIVE_OPTION = "i";

    /**
     * Paginate objects of the given class, which are assumed to be mapped to a Mongo collection, using the given
     * paging parameters.
     *
     * @param mongoTemplate the {@link MongoTemplate} that is used to perform the MongoDB operations
     * @param pagingParams  the parameters describing the desired pagination
     * @param clazz         the domain/model class mapped to a Mongo collection
     * @param            the result type
     * @param 

the pagination parameter type * @return a {@link Page} containing the paginated results */ public static Page paginate(MongoTemplate mongoTemplate, P pagingParams, Class clazz) { return paginate(mongoTemplate, pagingParams, clazz, KiwiBiConsumers.noOp()); } /** * Paginate objects of the given class, which are assumed to be mapped to a Mongo collection, using the given * paging parameters. *

* The {@code criteriaBuilder} is a {@link BiConsumer} that can be used to specify restriction criteria and/or * to access or change the pagination parameters. * * @param mongoTemplate the {@link MongoTemplate} that is used to perform the MongoDB operations * @param pagingParams the parameters describing the desired pagination * @param clazz the domain/model class mapped to a Mongo collection * @param criteriaBuilder a {@link BiConsumer} that can be used to add additional pagination and query criteria * @param the result type * @param

the pagination parameter type * @return a {@link Page} containing the paginated results, optionally filtered by criteria */ public static Page paginate(MongoTemplate mongoTemplate, P pagingParams, Class clazz, BiConsumer criteriaBuilder) { checkArgumentNotNull(mongoTemplate); checkArgumentNotNull(pagingParams); checkArgumentNotNull(clazz); checkArgumentNotNull(criteriaBuilder); if (isNull(pagingParams.getLimit()) || pagingParams.getLimit() < 1) { LOG.warn("No limit was supplied; setting it to 1. Supply a limit to avoid this warning"); pagingParams.setLimit(1); } if (isNull(pagingParams.getPage()) || pagingParams.getPage() < 0) { LOG.warn("No page number was supplied; setting it to 0. Supply a page number to avoid this warning"); pagingParams.setPage(0); } LOG.debug("Performing search using params: {}", pagingParams); var pageable = KiwiPaging.createPageable(pagingParams); var query = new PagingQuery(mongoTemplate).with(pageable); criteriaBuilder.accept(query, pagingParams); LOG.debug("Executing query: {}", query); return query.findPage(clazz); } /** * Add date restrictions to the given property. *

* Specify both start and end milliseconds (since the epoch) to create a closed range, or specify only start or * end milliseconds to create an open-range. E.g. if only start milliseconds is specified, then the criteria * includes only dates that are equal to or after the given value, with no upper bound. *

* If both start and end milliseconds are null, the call is a no-op. * * @param query the MongoDB query on which to add the criteria * @param propertyName the property name, which is expected to be of type {@link Date} * @param startDateInclusiveMillis the start date, inclusive. May be null. * @param endDateInclusiveMillis the end date, inclusive. May be null. */ public static void addDateBounds(Query query, String propertyName, @Nullable Long startDateInclusiveMillis, @Nullable Long endDateInclusiveMillis) { checkArgumentNotNull(query); checkArgumentNotNull(propertyName); if (isNull(startDateInclusiveMillis) && isNull(endDateInclusiveMillis)) { LOG.info("start and end are both null; ignoring"); return; } checkArgumentNotNull(propertyName, "property must not be null"); var datePropertyCriteria = Criteria.where(propertyName); // lower date bound if (nonNull(startDateInclusiveMillis)) { datePropertyCriteria.gte(new Date(startDateInclusiveMillis)); } // upper date bound if (nonNull(endDateInclusiveMillis)) { datePropertyCriteria.lte(new Date(endDateInclusiveMillis)); } query.addCriteria(datePropertyCriteria); } /** * Defines whether to require a partial or exact match. */ public enum PartialMatchType { /** * Permits regex matching in a case-insensitive manner. * * @see Criteria#regex(String) */ PARTIAL_MATCH, /** * Requires an equal match, and is case-sensitive. * * @see Criteria#is(Object) */ EQUAL_MATCH; /** * Convert the given string into a {@link PartialMatchType}, where "truthy" values are considered to * represent {@link #PARTIAL_MATCH}, and "falsy" values are considered to mean {@link #EQUAL_MATCH}. *

* Accepts various values, such as "true", "false", "yes", "no", etc. and null is treated as false. * * @param value the value to convert * @return the {@link PartialMatchType} * @implNote Uses {@link BooleanUtils#toString()} to perform the conversion */ public static PartialMatchType fromBooleanString(String value) { var allowPartialMatch = BooleanUtils.toBoolean(value); return from(allowPartialMatch); } /** * Convert the given boolean into a {@link PartialMatchType}. True is converted to {@link #PARTIAL_MATCH} * and false is converted to {@link #EQUAL_MATCH}. * * @param value the value to convert * @return the {@link PartialMatchType} */ public static PartialMatchType from(boolean value) { if (value) { return PARTIAL_MATCH; } return EQUAL_MATCH; } } /** * Add a partial or equal match criteria for the given property and match string. * * @param query the MongoDB query on which to add the criteria * @param matchString the string to match * @param propertyName the property name * @param matchType the desired match type */ public static void addPartialOrEqualMatchCriteria(Query query, String matchString, String propertyName, PartialMatchType matchType) { checkArgumentNotNull(query); checkArgumentNotNull(propertyName); checkArgumentNotNull(matchType); if (isBlank(matchString)) { LOG.info("matchString is blank; ignoring"); return; } Criteria matchCriteria; if (matchType == PartialMatchType.PARTIAL_MATCH) { matchCriteria = Criteria.where(propertyName).regex(ANY_STRING + matchString + ANY_STRING, CASE_INSENSITIVE_OPTION); } else { matchCriteria = Criteria.where(propertyName).is(matchString); } query.addCriteria(matchCriteria); } /** * Add a partial or equal match criteria for the given property and match strings. Any of the match strings * are considered to be a match, i.e. this effectively performs an OR operation. * * @param query the MongoDB query on which to add the criteria * @param matchStrings the strings to match, using an OR operation * @param propertyName the property name * @param matchType the desired match type */ public static void addMultiplePartialOrEqualMatchCriteria(Query query, Collection matchStrings, String propertyName, PartialMatchType matchType) { checkArgumentNotNull(query); checkArgumentNotNull(propertyName); checkArgumentNotNull(matchType); if (isNull(matchStrings) || matchStrings.isEmpty()) { LOG.info("matchStrings is null or empty; ignoring"); return; } Criteria matchCriteria; if (matchType == PartialMatchType.PARTIAL_MATCH) { var termCriteria = matchStrings .stream() .map(term -> Criteria.where(propertyName).regex(ANY_STRING + term + ANY_STRING, CASE_INSENSITIVE_OPTION)) .toArray(Criteria[]::new); matchCriteria = new Criteria().orOperator(termCriteria); } else { matchCriteria = Criteria.where(propertyName).in(matchStrings); } query.addCriteria(matchCriteria); } /** * Adds a {@link Criteria#in(Object...)} for the given property using values separated by commas in {@code csv}. * * @param query the MongoDB query on which to add the criteria * @param csv a comma-separated list of acceptable values * @param propertyName the property name */ public static void addInCriteriaFromCsv(Query query, String csv, String propertyName) { addInCriteriaFromCsv(query, csv, propertyName, Function.identity()); } /** * Adds a {@link Criteria#in(Object...)} for the given property using by first separating the values by comma in * {@code csv}, and then applying the given function to each value. * * @param query the MongoDB query on which to add the criteria * @param csv a comma-separated list of acceptable values * @param propertyName the property name * @param converter a function to convert the separated strings into a different type * @param the result type */ public static void addInCriteriaFromCsv(Query query, String csv, String propertyName, Function converter) { checkArgumentNotNull(query); checkArgumentNotNull(propertyName); checkArgumentNotNull(converter); if (isBlank(csv)) { LOG.info("csv is blank; ignoring"); return; } var values = KiwiStrings.nullSafeSplitOnCommas(csv); var convertedValues = values.stream().map(converter).collect(toList()); query.addCriteria(Criteria.where(propertyName).in(convertedValues)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy