From 801115eadaef522817667a9385061a388905451b Mon Sep 17 00:00:00 2001 From: itineric Date: Wed, 15 Jan 2025 19:18:59 +0100 Subject: [PATCH] Update spring and hibernate versions and apply appropriate fixes --- pom.xml | 4 +- rsql-common/pom.xml | 4 +- .../perplexhub/rsql/RSQLVisitorBase.java | 40 +++++++++++++------ rsql-jpa/pom.xml | 4 +- .../rsql/RSQLJPAPredicateConverter.java | 16 +++++--- .../io/github/perplexhub/rsql/SortUtils.java | 16 +++++--- .../perplexhub/rsql/jsonb/JsonbSupport.java | 2 +- .../perplexhub/rsql/RSQLJPASupportTest.java | 6 ++- .../github/perplexhub/rsql/Application.java | 5 ++- 9 files changed, 63 insertions(+), 34 deletions(-) diff --git a/pom.xml b/pom.xml index a40fa7b7..84ded526 100644 --- a/pom.xml +++ b/pom.xml @@ -23,8 +23,8 @@ 17 17 UTF-8 - 3.0.0 - 2022.0.0 + 3.4.1 + 2024.1.1 4.1.4 2.3.2 1.18.24 diff --git a/rsql-common/pom.xml b/rsql-common/pom.xml index 4424ab81..a1c347e5 100644 --- a/rsql-common/pom.xml +++ b/rsql-common/pom.xml @@ -34,8 +34,8 @@ io.hypersistence - hypersistence-utils-hibernate-62 - 3.5.1 + hypersistence-utils-hibernate-63 + 3.9.0 test diff --git a/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java b/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java index 8fe8eda6..2a06df1d 100644 --- a/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java +++ b/rsql-common/src/main/java/io/github/perplexhub/rsql/RSQLVisitorBase.java @@ -1,6 +1,7 @@ package io.github.perplexhub.rsql; import java.lang.reflect.*; +import java.sql.Timestamp; import java.time.*; import java.time.format.DateTimeParseException; import java.util.*; @@ -13,6 +14,7 @@ import jakarta.persistence.metamodel.PluralAttribute; import lombok.Getter; +import org.hibernate.metamodel.model.domain.ManagedDomainType; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.orm.jpa.vendor.Database; @@ -52,6 +54,15 @@ public static Database getDatabase(EntityManager entityManager) { return entityManagerDatabase.get(entityManager); } + public static Attribute getAttribute(String property, ManagedType classMetadata) { + // W/A found here: https://hibernate.atlassian.net/browse/HHH-18569 + // breaking change on hibernate side: https://github.com/hibernate/hibernate-orm/pull/6924#discussion_r1250474422 + if (classMetadata instanceof ManagedDomainType managedDomainType) { + return managedDomainType.findSubTypesAttribute(property); + } + return classMetadata.getAttribute(property); + } + protected abstract Map getPropertyPathMapper(); public Map, Map> getPropertyRemapping() { @@ -71,6 +82,9 @@ protected Object convert(String source, Class targetType) { object = UUID.fromString(source); } else if (targetType.equals(Date.class) || targetType.equals(java.sql.Date.class)) { object = java.sql.Date.valueOf(LocalDate.parse(source)); + } else if (targetType.equals(Timestamp.class)) { + Date date = java.sql.Date.valueOf(LocalDate.parse(source)); + return new Timestamp(date.getTime()); } else if (targetType.equals(LocalDate.class)) { object = LocalDate.parse(source); } else if (targetType.equals(LocalDateTime.class)) { @@ -158,10 +172,10 @@ protected String mapProperty(String selector, Class entityClass) { protected Class findPropertyType(String property, ManagedType classMetadata) { Class propertyType = null; - if (classMetadata.getAttribute(property).isCollection()) { - propertyType = ((PluralAttribute) classMetadata.getAttribute(property)).getBindableJavaType(); + if (getAttribute(property, classMetadata).isCollection()) { + propertyType = ((PluralAttribute) getAttribute(property, classMetadata)).getBindableJavaType(); } else { - propertyType = classMetadata.getAttribute(property).getJavaType(); + propertyType = getAttribute(property, classMetadata).getJavaType(); } return propertyType; } @@ -217,7 +231,7 @@ protected ManagedType getManagedElementCollectionType(String mappedProper protected boolean hasPropertyName(String property, ManagedType classMetadata) { try { - return classMetadata.getAttribute(property) != null; + return getAttribute(property, classMetadata) != null; } catch (IllegalArgumentException e) { return false; } @@ -238,30 +252,30 @@ protected static Class getElementCollectionGenericType(Class type, Attribute att } protected boolean isEmbeddedType(String property, ManagedType classMetadata) { - return classMetadata.getAttribute(property).getPersistentAttributeType() == PersistentAttributeType.EMBEDDED; + return getAttribute(property, classMetadata).getPersistentAttributeType() == PersistentAttributeType.EMBEDDED; } protected boolean isElementCollectionType(String property, ManagedType classMetadata) { - return classMetadata.getAttribute(property).getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION; + return getAttribute(property, classMetadata).getPersistentAttributeType() == PersistentAttributeType.ELEMENT_COLLECTION; } protected boolean isAssociationType(String property, ManagedType classMetadata) { - return classMetadata.getAttribute(property).isAssociation(); + return getAttribute(property, classMetadata).isAssociation(); } protected boolean isOneToOneAssociationType(String property, ManagedType classMetadata) { - return classMetadata.getAttribute(property).isAssociation() - && PersistentAttributeType.ONE_TO_ONE == classMetadata.getAttribute(property).getPersistentAttributeType(); + return getAttribute(property, classMetadata).isAssociation() + && PersistentAttributeType.ONE_TO_ONE == getAttribute(property, classMetadata).getPersistentAttributeType(); } protected boolean isOneToManyAssociationType(String property, ManagedType classMetadata) { - return classMetadata.getAttribute(property).isAssociation() - && PersistentAttributeType.ONE_TO_MANY == classMetadata.getAttribute(property).getPersistentAttributeType(); + return getAttribute(property, classMetadata).isAssociation() + && PersistentAttributeType.ONE_TO_MANY == getAttribute(property, classMetadata).getPersistentAttributeType(); } protected boolean isManyToManyAssociationType(String property, ManagedType classMetadata) { - return classMetadata.getAttribute(property).isAssociation() - && PersistentAttributeType.MANY_TO_MANY == classMetadata.getAttribute(property).getPersistentAttributeType(); + return getAttribute(property, classMetadata).isAssociation() + && PersistentAttributeType.MANY_TO_MANY == getAttribute(property, classMetadata).getPersistentAttributeType(); } static { diff --git a/rsql-jpa/pom.xml b/rsql-jpa/pom.xml index d39c81e0..a0f34862 100644 --- a/rsql-jpa/pom.xml +++ b/rsql-jpa/pom.xml @@ -54,8 +54,8 @@ io.hypersistence - hypersistence-utils-hibernate-60 - 3.5.2 + hypersistence-utils-hibernate-63 + 3.9.0 test diff --git a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java index 37f706e1..20414076 100644 --- a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java +++ b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/RSQLJPAPredicateConverter.java @@ -20,6 +20,7 @@ import cz.jirutka.rsql.parser.ast.OrNode; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.hibernate.query.criteria.JpaExpression; @Slf4j @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -81,7 +82,6 @@ private RSQLJPAContext findPropertyPathInternal(String propertyPath, Path startR Attribute attribute = null; String resolvedPropertyPath = firstTry? mapPropertyPath(propertyPath) : propertyPath; String[] properties = mapPropertyPath(resolvedPropertyPath).split("\\."); - for (int i = 0, propertiesLength = properties.length; i < propertiesLength; i++) { String property = properties[i]; String mappedProperty = mapProperty(property, classMetadata.getJavaType()); @@ -138,14 +138,14 @@ private RSQLJPAContext findPropertyPathInternal(String propertyPath, Path startR } } else if (isElementCollectionType(mappedProperty, classMetadata)) { String previousClass = classMetadata.getJavaType().getName(); - attribute = classMetadata.getAttribute(property); + attribute = RSQLVisitorBase.getAttribute(property, classMetadata); classMetadata = getManagedElementCollectionType(mappedProperty, classMetadata); String keyJoin = getKeyJoin(root, mappedProperty); log.debug("Create a element collection join between [{}] and [{}] using key [{}]", previousClass, classMetadata.getJavaType().getName(), keyJoin); root = join(keyJoin, root, mappedProperty); } else if (JsonbSupport.isJsonType(mappedProperty, classMetadata)) { root = root.get(mappedProperty); - attribute = classMetadata.getAttribute(mappedProperty); + attribute = RSQLVisitorBase.getAttribute(mappedProperty, classMetadata); break; } else { log.debug("Create property path for type [{}] property [{}]", classMetadata.getJavaType().getName(), mappedProperty); @@ -156,7 +156,7 @@ private RSQLJPAContext findPropertyPathInternal(String propertyPath, Path startR type = embeddedType; classMetadata = getManagedType(embeddedType); } else { - attribute = classMetadata.getAttribute(property); + attribute = RSQLVisitorBase.getAttribute(property, classMetadata); } } } @@ -244,7 +244,13 @@ private ResolvedExpression resolveExpression(ComparisonNode node, From root, Sel ComparisonNode jsonbNode = node.withSelector(jsonbPath); return JsonbSupport.jsonbPathExistsExpression(builder, jsonbNode, path); } else { - return ResolvedExpression.ofPath(path.as(String.class), String.class); + final Expression expression; + if (path instanceof JpaExpression jpaExpression) { + expression = jpaExpression.cast(String.class); + } else { + expression = path.as(String.class); + } + return ResolvedExpression.ofPath(expression, String.class); } } else { if (attribute != null diff --git a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/SortUtils.java b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/SortUtils.java index 8f8ce8c3..46d4f6e3 100644 --- a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/SortUtils.java +++ b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/SortUtils.java @@ -13,6 +13,7 @@ import jakarta.persistence.criteria.Order; import jakarta.persistence.criteria.Root; +import org.hibernate.query.criteria.JpaExpression; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -63,16 +64,16 @@ private static Order sortToJpaOrder(final String[] parts, final SortSupport sort sortSupport.getJoinHints(), sortSupport.getProcedureWhiteList(), sortSupport.getProcedureBlackList()); + final boolean ic = parts.length > 2 && "ic".equalsIgnoreCase(parts[2]); Expression propertyExpression = selector.getExpression((string, builder) ->{ final RSQLJPAContext rsqljpaContext = converter.findPropertyPath(string, root); final boolean isJson = JsonbSupport.isJsonType(rsqljpaContext.getAttribute()); return isJson - ? sortExpressionOfJson(rsqljpaContext, string, sortSupport.getPropertyPathMapper(), builder) + ? sortExpressionOfJson(rsqljpaContext, string, sortSupport.getPropertyPathMapper(), builder, ic) : rsqljpaContext.getPath(); }); - if (parts.length > 2 && "ic".equalsIgnoreCase(parts[2]) - && String.class.isAssignableFrom(propertyExpression.getJavaType())) { + if (ic && String.class.isAssignableFrom(propertyExpression.getJavaType())) { propertyExpression = cb.lower(propertyExpression.as(String.class)); } @@ -92,7 +93,8 @@ private static Order sortToJpaOrder(final String[] parts, final SortSupport sort private static Expression sortExpressionOfJson(RSQLJPAContext context, String property, Map mapping, - CriteriaBuilder builder) { + CriteriaBuilder builder, + boolean ic) { String path = PathUtils.expectBestMapping(property, mapping); String jsonbSelector = JsonbSupport.jsonPathOfSelector(context.getAttribute(), path); if(jsonbSelector.contains(".")) { @@ -102,7 +104,11 @@ private static Expression sortExpressionOfJson(RSQLJPAContext context, .skip(1) // skip root .map(builder::literal) .forEach(args::add); - return builder.function("jsonb_extract_path", String.class, args.toArray(Expression[]::new)); + Expression expression = builder.function("jsonb_extract_path", Object.class, args.toArray(Expression[]::new)); + if (ic && expression instanceof JpaExpression jpaExpression) { + expression = jpaExpression.cast(String.class); + } + return expression; } else { return context.getPath().as(String.class); } diff --git a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbSupport.java b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbSupport.java index b27eddc4..4c0e7be3 100644 --- a/rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbSupport.java +++ b/rsql-jpa/src/main/java/io/github/perplexhub/rsql/jsonb/JsonbSupport.java @@ -91,7 +91,7 @@ && isJsonColumn(attribute) * @return true if the attribute is a jsonb attribute */ public static boolean isJsonType(String mappedProperty, ManagedType classMetadata) { - return Optional.ofNullable(classMetadata.getAttribute(mappedProperty)) + return Optional.ofNullable(RSQLVisitorBase.getAttribute(mappedProperty, classMetadata)) .map(JsonbSupport::isJsonType) .orElse(false); } diff --git a/rsql-jpa/src/test/java/io/github/perplexhub/rsql/RSQLJPASupportTest.java b/rsql-jpa/src/test/java/io/github/perplexhub/rsql/RSQLJPASupportTest.java index c3bc94cc..4c39ec19 100644 --- a/rsql-jpa/src/test/java/io/github/perplexhub/rsql/RSQLJPASupportTest.java +++ b/rsql-jpa/src/test/java/io/github/perplexhub/rsql/RSQLJPASupportTest.java @@ -9,8 +9,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.*; @@ -764,9 +766,9 @@ final void testBetweenDate() { @Test final void testBetweenDateTime() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); - RSQLJPASupport.addConverter(Date.class, s -> { + RSQLJPASupport.addConverter(Timestamp.class, s -> { try { - return sdf.parse(s); + return new Timestamp(sdf.parse(s).getTime()); } catch (ParseException e) { return null; } diff --git a/rsql-querydsl-spring-boot-starter/src/test/java/io/github/perplexhub/rsql/Application.java b/rsql-querydsl-spring-boot-starter/src/test/java/io/github/perplexhub/rsql/Application.java index 5e9dba1e..f9f13864 100644 --- a/rsql-querydsl-spring-boot-starter/src/test/java/io/github/perplexhub/rsql/Application.java +++ b/rsql-querydsl-spring-boot-starter/src/test/java/io/github/perplexhub/rsql/Application.java @@ -1,5 +1,6 @@ package io.github.perplexhub.rsql; +import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.Date; @@ -23,9 +24,9 @@ public static void main(String[] args) throws Exception { @Bean public Object rsqlConfiguration(RSQLCommonSupport rsqlCommonSupport) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); - RSQLCommonSupport.addConverter(Date.class, s -> { + RSQLCommonSupport.addConverter(Timestamp.class, s -> { try { - return sdf.parse(s); + return new Timestamp(sdf.parse(s).getTime()); } catch (Exception e) { return null; }