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

org.kiwiproject.beta.dao.DaoHelpers Maven / Gradle / Ivy

package org.kiwiproject.beta.dao;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotBlank;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;

import com.google.common.annotations.Beta;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.kiwiproject.collect.KiwiArrays;
import org.kiwiproject.collect.KiwiLists;
import org.kiwiproject.spring.data.KiwiSort;
import org.kiwiproject.spring.data.KiwiSort.Direction;
import org.kiwiproject.spring.data.PagingRequest;
import org.springframework.data.domain.Sort;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

/**
 * Some simple utilities useful to data access code that is building queries,
 * for example, structured query languages such as SQL or HQL.
 * 

* This is useful in situations where you have a relatively static query, but * you need to add dynamically defined sorting/ordering. */ @Beta @UtilityClass @Slf4j public class DaoHelpers { /** * Defines/restricts the values that can be used when generating the ordering clause. * * @implNote Currently this is very restrictive and will only work in certain languages * such as SQL or HQL. */ private enum Connector { /** * Separates the "base" query from the order clause. */ ORDER_BY(" order by "), /** * Separator to use between fields when there is more than one sort field. */ SORT_FIELD_SEPARATOR(", "), /** * Separator to use between the sort field and sort direction. */ SORT_DIRECTION_SEPARATOR(" "); final String value; Connector(String value) { this.value = value; } } /** * Add sorts to the query restricting by the {@link AllowedFields} for the * {@link PagingRequest}. */ public static void addSorts(StringBuilder query, AllowedFields allowedSortFields, PagingRequest pagingRequest) { checkQueryNotNull(query); checkAllowedSortFieldsNotNull(allowedSortFields); checkArgumentNotNull(pagingRequest, "pagingRequest must not be null"); var primarySortDirection = toKiwiSortDirectionOrNull(pagingRequest.getPrimaryDirection()); var secondarySortDirection = toKiwiSortDirectionOrNull(pagingRequest.getSecondaryDirection()); addSorts(query, allowedSortFields, pagingRequest.getPrimarySort(), primarySortDirection, pagingRequest.getSecondarySort(), secondarySortDirection); } private static KiwiSort.Direction toKiwiSortDirectionOrNull(Sort.@Nullable Direction sortDirection) { return isNull(sortDirection) ? null : toKiwiSortDirection(sortDirection); } private static KiwiSort.Direction toKiwiSortDirection(Sort.Direction sortDirection) { checkArgumentNotNull(sortDirection); return sortDirection.isAscending() ? Direction.ASC : Direction.DESC; } /** * Add a sort to the query restricting by the {@link AllowedFields} for the * {@link KiwiSort}. */ public static void addSort(StringBuilder query, AllowedFields allowedSortFields, KiwiSort sort) { checkQueryNotNull(query); checkAllowedSortFieldsNotNull(allowedSortFields); checkArgumentNotNull(sort, "sort must not be null"); var primarySortDirection = toKiwiSortDirection(sort); addSort(query, allowedSortFields, sort.getProperty(), primarySortDirection); } /** * Add a sort to the query restricting by the {@link AllowedFields} * for the sort field and direction. */ public static void addSort(StringBuilder query, AllowedFields allowedSortFields, String sortField, @Nullable Direction sortDirection) { addSorts(query, allowedSortFields, sortField, sortDirection, null, null); } /** * Add sorts to the query restricting by the {@link AllowedFields} * for the primary and secondary sort criteria. */ public static void addSorts(StringBuilder query, AllowedFields allowedSortFields, KiwiSort primarySort, KiwiSort secondarySort) { checkArgumentNotNull(primarySort, "primarySort must not be null"); checkArgumentNotNull(secondarySort, "secondarySort must not be null"); var primarySortDirection = toKiwiSortDirection(primarySort); var secondarySortDirection = toKiwiSortDirection(secondarySort); addSorts(query, allowedSortFields, primarySort.getProperty(), primarySortDirection, secondarySort.getProperty(), secondarySortDirection); } /** * Add sorts to the query restricting by the {@link AllowedFields} * for the primary and secondary sort fields and directions. *

* This allows for the possibility that there are no sort criteria, in * which case the query is not modified. * * @implNote If a secondary sort is specified but not a primary sort, then * a warning is logged, the secondary sort is ignored, and therefore * the query is not modified. */ public static void addSorts( StringBuilder query, AllowedFields allowedSortFields, @Nullable String primarySortField, @Nullable Direction primarySortDirection, @Nullable String secondarySortField, @Nullable Direction secondarySortDirection) { checkQueryNotNull(query); checkAllowedSortFieldsNotNull(allowedSortFields); logWarningIfOnlySecondarySort(primarySortField, secondarySortField, secondarySortDirection); // Verify all sorts are valid before proceeding if (isNotBlank(primarySortField)) { allowedSortFields.assertAllowed(primarySortField); } if (isNotBlank(secondarySortField)) { allowedSortFields.assertAllowed(secondarySortField); } if (isNotBlank(primarySortField)) { addSort(query, allowedSortFields, Connector.ORDER_BY, primarySortField, primarySortDirection); if (isNotBlank(secondarySortField)) { addSort(query, allowedSortFields, Connector.SORT_FIELD_SEPARATOR, secondarySortField, secondarySortDirection); } } } private static void logWarningIfOnlySecondarySort(String primarySortField, String secondarySortField, @Nullable Direction secondarySortDirection) { if (onlyContainsSecondarySort(primarySortField, secondarySortField)) { LOG.warn("A secondary sort ({} {}) was specified without a primary sort. Ignoring.", secondarySortField, secondarySortDirection); } } private static boolean onlyContainsSecondarySort( @Nullable String primarySortField, @Nullable String secondarySortField) { return isBlank(primarySortField) && isNotBlank(secondarySortField); } /** * Adds sorts to the query restricting by the {@link AllowedFields} * for all the specified sorts. *

* This allows for the possibility that there are no sort criteria, in * which case the query is not modified. * * @implNote Any null values in the {@code sorts} array are filtered out */ public static void addSorts(StringBuilder query, AllowedFields allowedSortFields, KiwiSort... sorts) { checkQueryNotNull(query); checkAllowedSortFieldsNotNull(allowedSortFields); checkArgumentNotNull(sorts, "sorts (varargs) must not be null"); if (KiwiArrays.isNullOrEmpty(sorts)) { return; } addSorts(query, allowedSortFields, Arrays.asList(sorts)); } /** * Adds sorts to the query restricting by the {@link AllowedFields} * for all the specified sorts. *

* This allows for the possibility that there are no sort criteria, in * which case the query is not modified. * * @implNote Any null values in the {@code sorts} list are filtered out */ public static void addSorts(StringBuilder query, AllowedFields allowedSortFields, List sorts) { checkQueryNotNull(query); checkAllowedSortFieldsNotNull(allowedSortFields); checkArgumentNotNull(sorts, "sorts must not be null"); if (KiwiLists.isNullOrEmpty(sorts)) { return; } var nonNullSorts = sorts.stream().filter(Objects::nonNull).toList(); // Verify all sorts are valid before proceeding nonNullSorts.forEach(sort -> allowedSortFields.assertAllowed(sort.getProperty())); var firstSort = KiwiLists.first(nonNullSorts); addSort(query, allowedSortFields, Connector.ORDER_BY, firstSort.getProperty(), toKiwiSortDirection(firstSort)); var remainingSorts = KiwiLists.subListExcludingFirst(nonNullSorts); remainingSorts.forEach(sort -> addSort(query, allowedSortFields, Connector.SORT_FIELD_SEPARATOR, sort.getProperty(), toKiwiSortDirection(sort))); } /** * @implNote Eventually, KiwiSort should have a method to directly obtain the Direction object. * See the proposed KiwiSort feature here. * For now, we need to convert it manually from a String, and KiwiSort should never have a * null/blank value returned by getDirection() thus the state check below. Also, the value * returned from getDirection() should always be uppercase but be conservative and ensure it is. */ private static KiwiSort.Direction toKiwiSortDirection(KiwiSort sort) { checkArgumentNotNull(sort); checkState(isNotBlank(sort.getDirection()), "KiwiSort has a blank direction"); return KiwiSort.Direction.valueOf(sort.getDirection().toUpperCase(Locale.US)); } private static void addSort(StringBuilder query, AllowedFields allowedSortFields, Connector connector, String sortField, KiwiSort.@Nullable Direction sortDirection) { checkQueryNotNull(query); checkAllowedSortFieldsNotNull(allowedSortFields); checkArgumentNotNull(connector, "connector must not be blank"); checkArgumentNotBlank(sortField, "sortField must not be blank"); allowedSortFields.assertAllowed(sortField); query.append(connector.value) .append(allowedSortFields.getPrefixedFieldName(sortField)) .append(Connector.SORT_DIRECTION_SEPARATOR.value) .append(Optional.ofNullable(sortDirection).orElse(KiwiSort.Direction.ASC)); } private static void checkQueryNotNull(StringBuilder query) { checkArgumentNotNull(query, "query must not be null"); } private static void checkAllowedSortFieldsNotNull(AllowedFields allowedSortFields) { checkArgumentNotNull(allowedSortFields, "allowedSortFields must not be null"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy