From ee03f9b4a5facaee1b9d25313862e1d043f5a5dc Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Fri, 29 Mar 2019 14:22:48 +0100 Subject: [PATCH] DATAJPA-1519 - Final polishing. Introduced dedicated JPA SpEL root object to be able to explicitly control the function names exposed. --- .../config/JpaRepositoryConfigExtension.java | 13 +++---- .../jpa/repository/query/EscapeCharacter.java | 22 +++-------- .../jpa/repository/query/JpaQueryCreator.java | 2 +- .../query/QueryParameterSetterFactory.java | 1 - .../JpaEvaluationContextExtension.java | 39 ++++++++++++++++--- .../support/JpaRepositoryFactoryBean.java | 22 +++++++---- 6 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index b2a3802d8..1d74e5542 100644 --- a/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -214,16 +214,13 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi // EvaluationContextExtension for JPA specific SpEL functions - registerIfNotAlreadyRegistered(() -> { + Object value = getEscapeCharacter(config).orElse('\\'); - Object value = getEscapeCharacter(config).orElse('\\'); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JpaEvaluationContextExtension.class); + builder.addConstructorArgValue(value); - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JpaEvaluationContextExtension.class); - builder.addConstructorArgValue(value); - - return builder.getBeanDefinition(); - - }, registry, JpaEvaluationContextExtension.class.getName(), source); + registerIfNotAlreadyRegistered(builder.getBeanDefinition(), registry, JpaEvaluationContextExtension.class.getName(), + source); } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java b/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java index 7d382a4d0..f1bc0fd5f 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java @@ -34,30 +34,20 @@ public class EscapeCharacter { private static final List TO_REPLACE = Arrays.asList("_", "%"); - char value; + char escapeCharacter; /** * Escapes all special like characters ({@code _}, {@code %}) using the configured escape character. * - * @param value May be {@literal null}. + * @param value may be {@literal null}. * @return */ @Nullable public String escape(String value) { - if (value == null) { - return null; - } - return TO_REPLACE.stream() // - .reduce(value, (it, character) -> it.replace(character, this.value + character)); - } - - /** - * Makes the underlying character available. - * - * @return the value - */ - public char escapeCharacter() { - return value; + return value == null // + ? null // + : TO_REPLACE.stream() // + .reduce(value, (it, character) -> it.replace(character, this.escapeCharacter + character)); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 76bd83c95..e5bb9cd55 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -291,7 +291,7 @@ public class JpaQueryCreator extends AbstractQueryCreator stringPath = getTypedPath(root, part); Expression propertyExpression = upperIfIgnoreCase(stringPath); Expression parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression()); - Predicate like = builder.like(propertyExpression, parameterExpression, escape.getValue()); + Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter()); return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like; case TRUE: Expression truePath = getTypedPath(root, part); diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java index ed97c6d85..a04e8ebbd 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java @@ -28,7 +28,6 @@ import org.springframework.data.jpa.repository.query.StringQuery.ParameterBindin import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java index 686e430c2..55baa0555 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java @@ -15,8 +15,11 @@ */ package org.springframework.data.jpa.repository.support; +import lombok.RequiredArgsConstructor; + import org.springframework.data.jpa.repository.query.EscapeCharacter; -import org.springframework.data.spel.spi.EvaluationContextExtension; +import org.springframework.data.repository.query.spi.EvaluationContextExtension; +import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport; /** * {@link EvaluationContextExtension} to register {@link EscapeCharacter} as root object to essentially expose an @@ -24,9 +27,9 @@ import org.springframework.data.spel.spi.EvaluationContextExtension; * * @author Oliver Drotbohm */ -public class JpaEvaluationContextExtension implements EvaluationContextExtension { +public class JpaEvaluationContextExtension extends EvaluationContextExtensionSupport { - private final EscapeCharacter character; + private final JpaRootObject root; /** * Creates a new {@link JpaEvaluationContextExtension} for the given escape character. @@ -34,7 +37,7 @@ public class JpaEvaluationContextExtension implements EvaluationContextExtension * @param escapeCharacter the character to be used to escape parameters for LIKE expression. */ public JpaEvaluationContextExtension(char escapeCharacter) { - this.character = EscapeCharacter.of(escapeCharacter); + this.root = JpaRootObject.of(EscapeCharacter.of(escapeCharacter)); } /* @@ -52,6 +55,32 @@ public class JpaEvaluationContextExtension implements EvaluationContextExtension */ @Override public Object getRootObject() { - return character; + return root; + } + + @RequiredArgsConstructor(staticName = "of") + public static class JpaRootObject { + + private final EscapeCharacter character; + + /** + * Escapes the given source {@link String} for LIKE expressions. + * + * @param source can be {@literal null}. + * @return + * @see EscapeCharacter#escape(String) + */ + public String escape(String source) { + return character.escape(source); + } + + /** + * Returns the escape character being used to escape special characters for LIKE expressions. + * + * @return + */ + public char escapeCharacter() { + return character.getEscapeCharacter(); + } } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java index 4adea1549..280a15794 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java @@ -18,11 +18,11 @@ package org.springframework.data.jpa.repository.support; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; -import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -39,6 +39,8 @@ public class JpaRepositoryFactoryBean, S, ID> extends TransactionalRepositoryFactoryBeanSupport { private @Nullable EntityManager entityManager; + private EscapeCharacter escapeCharacter = EscapeCharacter.of('\\'); + /** * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. * @@ -48,6 +50,15 @@ public class JpaRepositoryFactoryBean, S, ID> super(repositoryInterface); } + /** + * Configures the escape character to be used to escape reserved characters in LIKE expressions. + * + * @param escapeCharacter + */ + public void setEscapeCharacter(char escapeCharacter) { + this.escapeCharacter = EscapeCharacter.of(escapeCharacter); + } + /** * The {@link EntityManager} to be used. * @@ -76,7 +87,7 @@ public class JpaRepositoryFactoryBean, S, ID> @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { - Assert.state(entityManager != null,"EntityManager must not be null!"); + Assert.state(entityManager != null, "EntityManager must not be null!"); return createRepositoryFactory(entityManager); } @@ -102,12 +113,7 @@ public class JpaRepositoryFactoryBean, S, ID> @Override public void afterPropertiesSet() { - Assert.state(entityManager != null,"EntityManager must not be null!"); + Assert.state(entityManager != null, "EntityManager must not be null!"); super.afterPropertiesSet(); } - - public void setEscapeCharacter(char escapeCharacter) { - - this.escapeCharacter = EscapeCharacter.of(escapeCharacter); - } }