org.springframework.data.relational.repository.query.CriteriaFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-data-relational Show documentation
Show all versions of spring-data-relational Show documentation
Spring Data Relational support
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.repository.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* Simple factory to contain logic to create {@link Criteria}s from {@link Part}s.
*
* @author Roman Chigvintsev
* @author Mark Paluch
*/
class CriteriaFactory {
private final ParameterMetadataProvider parameterMetadataProvider;
/**
* Creates new instance of this class with the given {@link ParameterMetadataProvider}.
*
* @param parameterMetadataProvider parameter metadata provider (must not be {@literal null})
*/
public CriteriaFactory(ParameterMetadataProvider parameterMetadataProvider) {
Assert.notNull(parameterMetadataProvider, "Parameter metadata provider must not be null!");
this.parameterMetadataProvider = parameterMetadataProvider;
}
/**
* Creates {@link Criteria} for the given {@link Part}.
*
* @param part method name part (must not be {@literal null})
* @return {@link Criteria} instance
* @throws IllegalArgumentException if part type is not supported
*/
public Criteria createCriteria(Part part) {
Part.Type type = part.getType();
String propertyName = part.getProperty().toDotPath();
Class propertyType = part.getProperty().getType();
Criteria.CriteriaStep criteriaStep = Criteria.where(propertyName);
if (type == Part.Type.IS_NULL || type == Part.Type.IS_NOT_NULL) {
return part.getType() == Part.Type.IS_NULL ? criteriaStep.isNull() : criteriaStep.isNotNull();
}
if (type == Part.Type.TRUE || type == Part.Type.FALSE) {
return part.getType() == Part.Type.TRUE ? criteriaStep.isTrue() : criteriaStep.isFalse();
}
switch (type) {
case BETWEEN: {
ParameterMetadata geParamMetadata = parameterMetadataProvider.next(part);
ParameterMetadata leParamMetadata = parameterMetadataProvider.next(part);
return criteriaStep.between(geParamMetadata.getValue(), leParamMetadata.getValue());
}
case AFTER:
case GREATER_THAN: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.greaterThan(paramMetadata.getValue());
}
case GREATER_THAN_EQUAL: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.greaterThanOrEquals(paramMetadata.getValue());
}
case BEFORE:
case LESS_THAN: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.lessThan(paramMetadata.getValue());
}
case LESS_THAN_EQUAL: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.lessThanOrEquals(paramMetadata.getValue());
}
case IN:
case NOT_IN: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
Criteria criteria = part.getType() == Part.Type.IN ? criteriaStep.in(asCollection(paramMetadata.getValue()))
: criteriaStep.notIn(asCollection(paramMetadata.getValue()));
return criteria.ignoreCase(shouldIgnoreCase(part) && checkCanUpperCase(part, part.getProperty().getType()));
}
case STARTING_WITH:
case ENDING_WITH:
case CONTAINING:
case NOT_CONTAINING:
case LIKE:
case NOT_LIKE: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
Criteria criteria = part.getType() == Part.Type.NOT_LIKE || part.getType() == Part.Type.NOT_CONTAINING
? criteriaStep.notLike(paramMetadata.getValue())
: criteriaStep.like(paramMetadata.getValue());
return criteria
.ignoreCase(shouldIgnoreCase(part) && checkCanUpperCase(part, propertyType, paramMetadata.getType()));
}
case SIMPLE_PROPERTY: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
if (paramMetadata.getValue() == null) {
return criteriaStep.isNull();
}
return criteriaStep.is(paramMetadata.getValue())
.ignoreCase(shouldIgnoreCase(part) && checkCanUpperCase(part, propertyType, paramMetadata.getType()));
}
case NEGATING_SIMPLE_PROPERTY: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.not(paramMetadata.getValue())
.ignoreCase(shouldIgnoreCase(part) && checkCanUpperCase(part, propertyType, paramMetadata.getType()));
}
default:
throw new IllegalArgumentException("Unsupported keyword " + type);
}
}
/**
* Checks whether comparison should be done in case-insensitive way.
*
* @param part method name part (must not be {@literal null})
* @return {@literal true} if comparison should be done in case-insensitive way
*/
private boolean shouldIgnoreCase(Part part) {
return part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS
|| part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE;
}
/**
* Checks whether "upper-case" conversion can be applied to the given {@link Expression}s in case the underlying
* {@link Part} requires ignoring case.
*
* @param part method name part (must not be {@literal null})
* @param expressionTypes types of the given expressions (must not be {@literal null} or empty)
* @throws IllegalStateException if {@link Part} requires ignoring case but "upper-case" conversion cannot be applied
* to at least one of the given {@link Expression}s
*/
private boolean checkCanUpperCase(Part part, Class... expressionTypes) {
Assert.notEmpty(expressionTypes, "Expression types must not be null or empty");
boolean strict = part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS;
for (Class expressionType : expressionTypes) {
if (!canUpperCase(expressionType)) {
if (strict) {
throw new IllegalStateException("Unable to ignore case of " + expressionType.getName()
+ " type, the property '" + part.getProperty().getSegment() + "' must reference a string");
}
return false;
}
}
return true;
}
private boolean canUpperCase(Class expressionType) {
return expressionType == String.class;
}
@SuppressWarnings("unchecked")
private static Collection