Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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<String, Object> processParameters(Map<String, Object> parameters,
Map<String, Object> queryParameters,
Condition condition,
@Nullable String entityName);

/**
* Returns a parameter value modified according to the given condition.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,6 +61,34 @@ public String generateWhere(ConditionGenerationContext context) {
: "";
}

@Override
public Map<String, Object> processParameters(Map<String, Object> parameters,
Map<String, Object> queryParameters,
Condition condition,
@Nullable String entityName) {
JpqlCondition jpqlCondition = (JpqlCondition) condition;

for (Map.Entry<String, Object> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -86,6 +87,21 @@ public String generateWhere(ConditionGenerationContext context) {
return sb.toString();
}

@Override
public Map<String, Object> processParameters(Map<String, Object> parameters,
Map<String, Object> 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, nestedCondition, entityName);
}

return parameters;
}

@Nullable
@Override
public Object generateParameterValue(@Nullable Condition condition, @Nullable Object parameterValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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.
*
Expand All @@ -58,88 +44,11 @@ public void setInIntervalResolver(InIntervalParametersResolver inIntervalResolve
* @param actualized an actualized condition
* @return modified parameters
*/
public Map<String, Object> processParameters(Map<String, Object> parameters, Map<String, Object> queryParameters,
Condition actualized, @Nullable String entityName) {
List<PropertyCondition> 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<Pair<String, Object>> 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<JpqlCondition> jpqlConditions = collectNestedJpqlConditions(actualized);
for (JpqlCondition jpqlCondition : jpqlConditions) {
for (Map.Entry<String, Object> 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<PropertyCondition> collectNestedPropertyConditions(Condition rootCondition) {
List<PropertyCondition> 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<JpqlCondition> collectNestedJpqlConditions(Condition rootCondition) {
List<JpqlCondition> 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<String, Object> processParameters(Map<String, Object> parameters,
Map<String, Object> queryParameters,
Condition actualized,
@Nullable String entityName) {
ConditionGenerator conditionGenerator = resolver.getConditionGenerator(new ConditionGenerationContext(actualized));
return conditionGenerator.processParameters(parameters, queryParameters, actualized, entityName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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<String, Object> processParameters(Map<String, Object> parameters,
Map<String, Object> 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<Pair<String, Object>> 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
Expand Down