From 34867ee7e2e772b50ed2c32a4223495a24490759 Mon Sep 17 00:00:00 2001 From: Dmitry Godlevskiy Date: Fri, 27 Mar 2026 23:49:22 -0300 Subject: [PATCH 1/3] Moved processParameter logic of ParameterJpqlGenerator to ConditionGenerator implementations --- .../jpql/generator/ConditionGenerator.java | 16 ++- .../generator/JpqlConditionGenerator.java | 31 +++++- .../generator/LogicalConditionGenerator.java | 18 ++- .../generator/ParameterJpqlGenerator.java | 105 ++---------------- .../generator/PropertyConditionGenerator.java | 51 ++++++++- 5 files changed, 119 insertions(+), 102 deletions(-) diff --git a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ConditionGenerator.java b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ConditionGenerator.java index aa8e110b04..538f8ee86a 100644 --- a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ConditionGenerator.java +++ b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ConditionGenerator.java @@ -17,9 +17,10 @@ package io.jmix.data.impl.jpql.generator; import io.jmix.core.querycondition.Condition; - import org.jspecify.annotations.Nullable; +import java.util.Map; + /** * Modifies parts of JPQL query */ @@ -49,6 +50,19 @@ public interface ConditionGenerator { */ String generateWhere(ConditionGenerationContext context); + /** + * Returns parameters modified according to the given condition. + * + * @param parameters result parameters + * @param queryParameters query parameters + * @param condition the condition + * @return modified parameters + */ + Map processParameters(Map parameters, + Map queryParameters, + Condition condition, + @Nullable String entityName); + /** * Returns a parameter value modified according to the given condition. * diff --git a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/JpqlConditionGenerator.java b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/JpqlConditionGenerator.java index fb2f22773d..e08315f2ad 100644 --- a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/JpqlConditionGenerator.java +++ b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/JpqlConditionGenerator.java @@ -21,10 +21,11 @@ import io.jmix.core.QueryUtils; import io.jmix.core.querycondition.Condition; import io.jmix.core.querycondition.JpqlCondition; +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.jspecify.annotations.Nullable; +import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,6 +61,34 @@ public String generateWhere(ConditionGenerationContext context) { : ""; } + @Override + public Map processParameters(Map parameters, + Map queryParameters, + Condition condition, + @Nullable String entityName) { + JpqlCondition jpqlCondition = (JpqlCondition) condition; + + for (Map.Entry parameter : jpqlCondition.getParameterValuesMap().entrySet()) { + // JpqlCondition may take a value from queryParameters collection or from the + // JpqlCondition.parameterValuesMap attribute. queryParameters value has higher priority. + Object parameterValue; + String parameterName = parameter.getKey(); + if (!queryParameters.containsKey(parameterName) || queryParameters.get(parameterName) == null) { + // Modify the query parameter value (e.g. wrap value from JpqlFilter for "contains" + // jpql operation) + parameterValue = generateParameterValue(jpqlCondition, parameter.getValue(), entityName); + } else { + // In other cases, it is assumed that the value has already been modified + // (e.g. wrapped value from DataLoadCoordinator) + parameterValue = queryParameters.get(parameter.getKey()); + } + + parameters.put(parameter.getKey(), parameterValue); + } + + return parameters; + } + @Nullable @Override public Object generateParameterValue(@Nullable Condition condition, @Nullable Object parameterValue, diff --git a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java index be43b1de75..02cccc5d37 100644 --- a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java +++ b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java @@ -20,12 +20,13 @@ import io.jmix.core.JmixOrder; import io.jmix.core.querycondition.Condition; import io.jmix.core.querycondition.LogicalCondition; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Component("data_LogicalConditionGenerator") @@ -86,6 +87,21 @@ public String generateWhere(ConditionGenerationContext context) { return sb.toString(); } + @Override + public Map processParameters(Map parameters, + Map queryParameters, + Condition condition, + @Nullable String entityName) { + LogicalCondition logicalCondition = (LogicalCondition) condition; + + for (Condition nestedCondition : logicalCondition.getConditions()) { + ConditionGenerator generator = resolver.getConditionGenerator(new ConditionGenerationContext(nestedCondition)); + parameters = generator.processParameters(parameters, queryParameters, condition, entityName); + } + + return parameters; + } + @Nullable @Override public Object generateParameterValue(@Nullable Condition condition, @Nullable Object parameterValue, diff --git a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ParameterJpqlGenerator.java b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ParameterJpqlGenerator.java index abcbe5a003..e636927cae 100644 --- a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ParameterJpqlGenerator.java +++ b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/ParameterJpqlGenerator.java @@ -16,19 +16,11 @@ package io.jmix.data.impl.jpql.generator; -import io.jmix.core.common.datastruct.Pair; import io.jmix.core.querycondition.Condition; -import io.jmix.core.querycondition.JpqlCondition; -import io.jmix.core.querycondition.LogicalCondition; -import io.jmix.core.querycondition.PropertyCondition; -import io.jmix.core.querycondition.PropertyConditionUtils; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.jspecify.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; import java.util.Map; /** @@ -38,18 +30,12 @@ public class ParameterJpqlGenerator { protected ConditionGeneratorResolver resolver; - protected InIntervalParametersResolver inIntervalResolver; @Autowired public ParameterJpqlGenerator(ConditionGeneratorResolver resolver) { this.resolver = resolver; } - @Autowired(required = false) - public void setInIntervalResolver(InIntervalParametersResolver inIntervalResolver) { - this.inIntervalResolver = inIntervalResolver; - } - /** * Returns parameters for JPQL query modified according to the given tree of conditions. * @@ -58,88 +44,11 @@ public void setInIntervalResolver(InIntervalParametersResolver inIntervalResolve * @param actualized an actualized condition * @return modified parameters */ - public Map processParameters(Map parameters, Map queryParameters, - Condition actualized, @Nullable String entityName) { - List propertyConditions = collectNestedPropertyConditions(actualized); - for (PropertyCondition propertyCondition : propertyConditions) { - String parameterName = propertyCondition.getParameterName(); - if (PropertyConditionUtils.isUnaryOperation(propertyCondition)) { - //remove query parameter for unary operations (e.g. IS_NULL) - parameters.remove(parameterName); - } else if (PropertyConditionUtils.isInIntervalOperation(propertyCondition)) { - //remove query parameter for "in interval" operations - parameters.remove(parameterName); - - if (inIntervalResolver != null) { - // trying to resolve parameters for "in interval date between" operation - List> inIntervalParameters = inIntervalResolver.resolveParameters(propertyCondition); - if (!inIntervalParameters.isEmpty()) { - inIntervalParameters.forEach(p -> parameters.put(p.getFirst(), p.getSecond())); - } - } - } else { - //PropertyCondition may take a value from queryParameters collection or from the - //PropertyCondition.parameterValue attribute. queryParameters has higher priority. - Object parameterValue; - if (!queryParameters.containsKey(parameterName) || queryParameters.get(parameterName) == null) { - parameterValue = generateParameterValue(propertyCondition, propertyCondition.getParameterValue(), entityName); - } else { - //modify the query parameter value (e.g. wrap value for "contains" jpql operation) - Object queryParameterValue = queryParameters.get(parameterName); - parameterValue = generateParameterValue(propertyCondition, queryParameterValue, entityName); - } - parameters.put(parameterName, parameterValue); - } - } - - List jpqlConditions = collectNestedJpqlConditions(actualized); - for (JpqlCondition jpqlCondition : jpqlConditions) { - for (Map.Entry parameter : jpqlCondition.getParameterValuesMap().entrySet()) { - // JpqlCondition may take a value from queryParameters collection or from the - // JpqlCondition.parameterValuesMap attribute. queryParameters value has higher priority. - Object parameterValue; - String parameterName = parameter.getKey(); - if (!queryParameters.containsKey(parameterName) || queryParameters.get(parameterName) == null) { - // Modify the query parameter value (e.g. wrap value from JpqlFilter for "contains" - // jpql operation) - parameterValue = generateParameterValue(jpqlCondition, parameter.getValue(), entityName); - } else { - // In other cases, it is assumed that the value has already been modified - // (e.g. wrapped value from DataLoadCoordinator) - parameterValue = queryParameters.get(parameter.getKey()); - } - parameters.put(parameter.getKey(), parameterValue); - } - } - - return parameters; - } - - protected List collectNestedPropertyConditions(Condition rootCondition) { - List propertyConditions = new ArrayList<>(); - if (rootCondition instanceof LogicalCondition) { - ((LogicalCondition) rootCondition).getConditions().forEach(c -> - propertyConditions.addAll(collectNestedPropertyConditions(c))); - } else if (rootCondition instanceof PropertyCondition) { - propertyConditions.add((PropertyCondition) rootCondition); - } - return propertyConditions; - } - - protected List collectNestedJpqlConditions(Condition rootCondition) { - List jpqlConditions = new ArrayList<>(); - if (rootCondition instanceof LogicalCondition) { - ((LogicalCondition) rootCondition).getConditions().forEach(c -> - jpqlConditions.addAll(collectNestedJpqlConditions(c))); - } else if (rootCondition instanceof JpqlCondition) { - jpqlConditions.add((JpqlCondition) rootCondition); - } - return jpqlConditions; - } - - @Nullable - protected Object generateParameterValue(Condition condition, @Nullable Object parameterValue, @Nullable String entityName) { - ConditionGenerator conditionGenerator = resolver.getConditionGenerator(new ConditionGenerationContext(condition)); - return conditionGenerator.generateParameterValue(condition, parameterValue, entityName); + public Map processParameters(Map parameters, + Map queryParameters, + Condition actualized, + @Nullable String entityName) { + ConditionGenerator conditionGenerator = resolver.getConditionGenerator(new ConditionGenerationContext(actualized)); + return conditionGenerator.processParameters(parameters, queryParameters, actualized, entityName); } } diff --git a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/PropertyConditionGenerator.java b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/PropertyConditionGenerator.java index 9767fc12f2..934965f541 100644 --- a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/PropertyConditionGenerator.java +++ b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/PropertyConditionGenerator.java @@ -21,6 +21,7 @@ import io.jmix.core.Metadata; import io.jmix.core.MetadataTools; import io.jmix.core.QueryUtils; +import io.jmix.core.common.datastruct.Pair; import io.jmix.core.entity.EntityValues; import io.jmix.core.metamodel.model.MetaClass; import io.jmix.core.metamodel.model.MetaProperty; @@ -29,12 +30,14 @@ import io.jmix.core.querycondition.PropertyCondition; import io.jmix.core.querycondition.PropertyConditionUtils; import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.jspecify.annotations.Nullable; +import java.util.List; +import java.util.Map; import static io.jmix.core.metamodel.model.MetaProperty.Type.ASSOCIATION; import static io.jmix.core.metamodel.model.MetaProperty.Type.COMPOSITION; @@ -46,6 +49,9 @@ public class PropertyConditionGenerator implements ConditionGenerator { protected MetadataTools metadataTools; protected Metadata metadata; + @Nullable + protected InIntervalParametersResolver inIntervalResolver; + @Value("${jmix.eclipselink.use-inner-join-in-condition:false}") protected boolean useInnerJoinInCondition; @@ -58,6 +64,11 @@ public PropertyConditionGenerator(MetadataTools metadataTools, Metadata metadata this.metadata = metadata; } + @Autowired(required = false) + public void setInIntervalResolver(InIntervalParametersResolver inIntervalResolver) { + this.inIntervalResolver = inIntervalResolver; + } + @Override public boolean supports(ConditionGenerationContext context) { return context.getCondition() instanceof PropertyCondition; @@ -146,7 +157,45 @@ public String generateWhere(ConditionGenerationContext context) { String property = getProperty(propertyCondition.getProperty(), context.getEntityName()); return generateWhere(propertyCondition, entityAlias, property, context.isElementCollection()); } + } + + @Override + public Map processParameters(Map parameters, + Map queryParameters, + Condition condition, + @Nullable String entityName) { + PropertyCondition propertyCondition = (PropertyCondition) condition; + + String parameterName = propertyCondition.getParameterName(); + if (PropertyConditionUtils.isUnaryOperation(propertyCondition)) { + //remove query parameter for unary operations (e.g. IS_NULL) + parameters.remove(parameterName); + } else if (PropertyConditionUtils.isInIntervalOperation(propertyCondition)) { + //remove query parameter for "in interval" operations + parameters.remove(parameterName); + + if (inIntervalResolver != null) { + // trying to resolve parameters for "in interval date between" operation + List> inIntervalParameters = inIntervalResolver.resolveParameters(propertyCondition); + if (!inIntervalParameters.isEmpty()) { + inIntervalParameters.forEach(p -> parameters.put(p.getFirst(), p.getSecond())); + } + } + } else { + //PropertyCondition may take a value from queryParameters collection or from the + //PropertyCondition.parameterValue attribute. queryParameters has higher priority. + Object parameterValue; + if (!queryParameters.containsKey(parameterName) || queryParameters.get(parameterName) == null) { + parameterValue = generateParameterValue(propertyCondition, propertyCondition.getParameterValue(), entityName); + } else { + //modify the query parameter value (e.g. wrap value for "contains" jpql operation) + Object queryParameterValue = queryParameters.get(parameterName); + parameterValue = generateParameterValue(propertyCondition, queryParameterValue, entityName); + } + parameters.put(parameterName, parameterValue); + } + return parameters; } @Nullable From 5b7987efaab4ccd61c6cca5533cc632fd70e7141 Mon Sep 17 00:00:00 2001 From: Dmitry Godlevskiy Date: Fri, 27 Mar 2026 23:58:13 -0300 Subject: [PATCH 2/3] Added isCaseInsensitive method to PropertyConditionUtils --- .../querycondition/PropertyConditionUtils.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jmix-core/core/src/main/java/io/jmix/core/querycondition/PropertyConditionUtils.java b/jmix-core/core/src/main/java/io/jmix/core/querycondition/PropertyConditionUtils.java index a700bffe5e..3acd15ec4c 100644 --- a/jmix-core/core/src/main/java/io/jmix/core/querycondition/PropertyConditionUtils.java +++ b/jmix-core/core/src/main/java/io/jmix/core/querycondition/PropertyConditionUtils.java @@ -71,6 +71,20 @@ public static boolean isMemberOfCollectionOperation(PropertyCondition propertyCo || PropertyCondition.Operation.NOT_MEMBER_OF_COLLECTION.equals(operation); } + /** + * Checks if the property condition's operation is case-insensitive. + * @param condition property condition + * @return true if the operation is case-insensitive, otherwise - false + */ + public static boolean isCaseInsensitiveOperation(PropertyCondition condition) { + String operation = condition.getOperation(); + + return PropertyCondition.Operation.CONTAINS.equals(operation) + || PropertyCondition.Operation.NOT_CONTAINS.equals(operation) + || PropertyCondition.Operation.STARTS_WITH.equals(operation) + || PropertyCondition.Operation.ENDS_WITH.equals(operation); + } + /** * @param property an entity property * @return a parameter name From bac31e1efc24320d5068285e342b0990ce440243 Mon Sep 17 00:00:00 2001 From: Dmitry Godlevskiy Date: Sat, 28 Mar 2026 19:10:45 -0300 Subject: [PATCH 3/3] Fix wrong nestedCondition argument in LogicalConditionGenerator --- .../data/impl/jpql/generator/LogicalConditionGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java index 02cccc5d37..1ddb052afe 100644 --- a/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java +++ b/jmix-data/data/src/main/java/io/jmix/data/impl/jpql/generator/LogicalConditionGenerator.java @@ -96,7 +96,7 @@ public Map processParameters(Map parameters, for (Condition nestedCondition : logicalCondition.getConditions()) { ConditionGenerator generator = resolver.getConditionGenerator(new ConditionGenerationContext(nestedCondition)); - parameters = generator.processParameters(parameters, queryParameters, condition, entityName); + parameters = generator.processParameters(parameters, queryParameters, nestedCondition, entityName); } return parameters;